mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-22 16:17:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
227ff4e13a | ||
|
|
8e6e70683e | ||
|
|
626f0aec6a | ||
|
|
973d174a30 | ||
|
|
9f35690b95 | ||
|
|
d961f2bc13 | ||
|
|
10e8277044 | ||
|
|
b4c40d9b18 | ||
|
|
e1603f0808 | ||
|
|
d4c2350d63 | ||
|
|
5e5470ae12 | ||
|
|
6230a99360 | ||
|
|
801adb6af7 | ||
|
|
8b9b96f422 | ||
|
|
160684f23e | ||
|
|
88930a9de4 | ||
|
|
af6b8c9b88 | ||
|
|
bf52d3189d | ||
|
|
5d872e8b5b | ||
|
|
4832d8007b | ||
|
|
879e084383 | ||
|
|
1ae16bb657 | ||
|
|
2177bc68db | ||
|
|
c8b887fe32 | ||
|
|
7205242f38 | ||
|
|
be33b5b387 | ||
|
|
66a123f4b9 | ||
|
|
76a4ee22b3 | ||
|
|
3a6bf4d20b | ||
|
|
09cf105cdc | ||
|
|
ce02e086a3 | ||
|
|
e8d4313454 | ||
|
|
49046f88cf | ||
|
|
5e2f32e3eb | ||
|
|
b4a1393bf6 | ||
|
|
0ccee9d806 | ||
|
|
5700272dc4 | ||
|
|
7e5a3925b3 | ||
|
|
1febe32e73 | ||
|
|
6059c982cf | ||
|
|
de7b973239 | ||
|
|
741113a607 | ||
|
|
c20daa8867 | ||
|
|
5e57a7d5c0 | ||
|
|
7c8d27700e | ||
|
|
f8621be5ee | ||
|
|
8ed87e10b4 | ||
|
|
ca26fa2dd0 | ||
|
|
1669095fb1 | ||
|
|
13eed48877 | ||
|
|
07494e6eff | ||
|
|
41b1d72825 | ||
|
|
4d2965e74f | ||
|
|
12526bfca4 | ||
|
|
3e2c344140 | ||
|
|
33730dd584 | ||
|
|
1cb03b1882 | ||
|
|
2f0a6db414 | ||
|
|
a42328d971 | ||
|
|
96ff51d598 | ||
|
|
7c3d3af681 | ||
|
|
6942bca5a7 | ||
|
|
9eac9a190e | ||
|
|
40e4fbd9a6 | ||
|
|
7b6996ed80 | ||
|
|
ef2827a9d8 | ||
|
|
dbf98ce3a6 | ||
|
|
ee7cb5123d | ||
|
|
77e1d6ab8a | ||
|
|
1640bed7a5 | ||
|
|
c6fd5cbbce | ||
|
|
5288041da5 | ||
|
|
a848fb1dd1 | ||
|
|
172945434c | ||
|
|
a0967e0281 | ||
|
|
deab717c73 | ||
|
|
5db5a088ea | ||
|
|
5e106d2f19 | ||
|
|
940da6bc73 | ||
|
|
d110bff253 | ||
|
|
4caa329c36 | ||
|
|
7c7d279337 | ||
|
|
831e6ee76d | ||
|
|
b01842ad02 | ||
|
|
7acf91883a | ||
|
|
897f6fdd3e | ||
|
|
6f9c3fbc38 | ||
|
|
bcbd1a5b4b | ||
|
|
6468efd7e4 | ||
|
|
66863789c7 | ||
|
|
b30058fc29 | ||
|
|
a41cd0b070 | ||
|
|
ea8b905f02 | ||
|
|
16b301236b | ||
|
|
22162dda83 | ||
|
|
024af89be3 | ||
|
|
e1a697658e | ||
|
|
0a5051f959 | ||
|
|
82b6b60b31 | ||
|
|
06b3c3636a | ||
|
|
e5712a9474 | ||
|
|
6ff845c53b | ||
|
|
1c225ed04d | ||
|
|
612b738968 | ||
|
|
87356ef594 | ||
|
|
20a4dfa6d6 | ||
|
|
524cc97f51 | ||
|
|
fc849fb51a | ||
|
|
c88651939a | ||
|
|
01254f67f0 | ||
|
|
212ed33b21 | ||
|
|
bd3f8aef21 | ||
|
|
3785acea29 | ||
|
|
866e2f1762 | ||
|
|
0a705d6752 | ||
|
|
4f74fd5354 |
No files matched your search
@@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us address broken functionality
|
about: Create a report to help us address issues in Moonlight
|
||||||
|
|
||||||
---
|
---
|
||||||
|
**READ ME FIRST!**
|
||||||
|
This issue tracker is not the right place to ask for troubleshooting help if you're not able to connect or stream at all. For those requests, please use the #pc-help channel on our Discord server where there are many more people who can help (or direct you back here if it looks like a Moonlight bug after all). https://discord.gg/MySTSdq
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
@@ -16,6 +18,9 @@ If applicable, add screenshots to help explain your problem. If the issue is rel
|
|||||||
**Affected games**
|
**Affected games**
|
||||||
List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
|
List the games you've tried that exhibit the issue. To see if the issue is game-specific, try streaming Steam Big Picture with Moonlight and see if the issue persists there.
|
||||||
|
|
||||||
|
**Other Moonlight clients**
|
||||||
|
- Does the issue occur when using Moonlight on iOS or Android?
|
||||||
|
|
||||||
**Moonlight settings (please complete the following information)**
|
**Moonlight settings (please complete the following information)**
|
||||||
- Have any settings been adjusted from defaults?
|
- Have any settings been adjusted from defaults?
|
||||||
- If so, which settings have been changed?
|
- If so, which settings have been changed?
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
issuesOpened: >
|
||||||
|
If this is a question about Moonlight or you need help troubleshooting a streaming problem, please use the help channels on our [Discord server](https://discord.gg/MySTSdq) instead of GitHub issues. There are many more people available on Discord to help you and answer your questions.<br /><br />
|
||||||
|
This issue tracker should only be used for specific bugs or feature requests.<br /><br />
|
||||||
|
Thank you, and happy streaming!
|
||||||
+43
-24
@@ -3,51 +3,70 @@ language: cpp
|
|||||||
git:
|
git:
|
||||||
depth: 1
|
depth: 1
|
||||||
|
|
||||||
env:
|
|
||||||
- NJOBS=2
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
compiler: clang
|
compiler: clang
|
||||||
|
env: TRAVIS_TARGET=osx
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- qt
|
||||||
- os: linux
|
- os: linux
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: xenial
|
dist: xenial
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
|
env: TRAVIS_TARGET=steamlink
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- libc6:i386
|
||||||
|
- libstdc++6:i386
|
||||||
|
- os: linux
|
||||||
|
sudo: required
|
||||||
|
dist: bionic
|
||||||
|
compiler: gcc
|
||||||
|
env: TRAVIS_TARGET=linux
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
- sourceline: 'ppa:beineri/opt-qt597-xenial'
|
- sourceline: 'ppa:savoury1/multimedia'
|
||||||
- sourceline: 'ppa:jonathonf/ffmpeg-4'
|
- sourceline: 'ppa:savoury1/ffmpeg4'
|
||||||
packages:
|
packages:
|
||||||
- qt59base
|
- qt5-default
|
||||||
- qt59quickcontrols2
|
- qtbase5-dev
|
||||||
- qt59svg
|
- qtquickcontrols2-5-dev
|
||||||
- libesd0-dev
|
- libqt5svg5-dev
|
||||||
- libgl-dev
|
- libgl1-mesa-dev
|
||||||
- libegl1-mesa-dev
|
- libegl1-mesa-dev
|
||||||
- libxkbcommon-dev
|
- libxkbcommon-dev
|
||||||
- wayland-protocols
|
- wayland-protocols
|
||||||
- libopus-dev
|
- libopus-dev
|
||||||
- libavcodec-dev
|
|
||||||
- libavutil-dev
|
|
||||||
- libva-dev
|
- libva-dev
|
||||||
- libvdpau-dev
|
- libvdpau-dev
|
||||||
|
- libsdl2-dev
|
||||||
|
- qtdeclarative5-dev
|
||||||
|
- libssl-dev
|
||||||
|
- libsdl2-ttf-dev
|
||||||
|
- libavcodec-dev
|
||||||
|
- libavutil-dev
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- '[ "$TRAVIS_OS_NAME" != osx ] || brew install qt5'
|
- '[ "$TRAVIS_TARGET" != osx ] || brew link --force qt'
|
||||||
- '[ "$TRAVIS_OS_NAME" != osx ] || brew link --force qt5'
|
- if [[ "$TRAVIS_TARGET" = "osx" ]]; then nvm use node && npm install --global create-dmg; fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then export SDL2_VER=2.0.9 && wget https://www.libsdl.org/release/SDL2-$SDL2_VER.tar.gz && tar -xvf SDL2-$SDL2_VER.tar.gz && cd SDL2-$SDL2_VER && ./configure && make -j$NJOBS && sudo make install && cd ..; fi
|
- if [[ "$TRAVIS_TARGET" = "steamlink" ]]; then git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk; fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then export SDL2_TTF_VER=2.0.15 && wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-$SDL2_TTF_VER.tar.gz && tar -xvf SDL2_ttf-$SDL2_TTF_VER.tar.gz && cd SDL2_ttf-$SDL2_TTF_VER && ./configure && make -j$NJOBS && sudo make install && cd ..; fi
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- '[ "$TRAVIS_OS_NAME" != "linux" ] || source /opt/qt59/bin/qt59-env.sh'
|
- '[ "$TRAVIS_TARGET" = "steamlink" ] || qmake -v'
|
||||||
- qmake -v
|
- '[[ "$TRAVIS_TARGET" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
|
||||||
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != clang++ ]] || export QMAKESPEC=linux-clang'
|
- '[[ "$TRAVIS_TARGET" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
|
||||||
- '[[ "$TRAVIS_OS_NAME" != linux || "$CXX" != g++ ]] || export QMAKESPEC=linux-g++'
|
- '[ "$TRAVIS_TARGET" != osx ] || export QMAKESPEC=macx-clang'
|
||||||
- '[ "$TRAVIS_OS_NAME" != osx ] || export QMAKESPEC=macx-clang'
|
- '[ "$TRAVIS_TARGET" != "steamlink" ] || export STEAMLINK_SDK_PATH=$HOME/steamlink-sdk'
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- qmake moonlight-qt.pro -spec $QMAKESPEC
|
- '[ "$TRAVIS_TARGET" != "linux" ] || qmake moonlight-qt.pro -spec $QMAKESPEC'
|
||||||
- make -j$NJOBS debug
|
- '[ "$TRAVIS_TARGET" != "linux" ] || make -j$(nproc) debug'
|
||||||
- make -j$NJOBS release
|
- '[ "$TRAVIS_TARGET" != "linux" ] || make -j$(nproc) release'
|
||||||
|
- '[ "$TRAVIS_TARGET" != "osx" ] || scripts/generate-dmg.sh Release'
|
||||||
|
- '[ "$TRAVIS_TARGET" != "steamlink" ] || scripts/build-steamlink-app.sh'
|
||||||
@@ -135,10 +135,45 @@ private:
|
|||||||
// D3D9 exclusive fullscreen in a way that causes CreateDeviceEx()
|
// D3D9 exclusive fullscreen in a way that causes CreateDeviceEx()
|
||||||
// to deadlock. https://github.com/moonlight-stream/moonlight-qt/issues/102
|
// to deadlock. https://github.com/moonlight-stream/moonlight-qt/issues/102
|
||||||
L"NahimicOSD.dll",
|
L"NahimicOSD.dll",
|
||||||
|
|
||||||
// This DLL has been seen in several crash reports. Some Googling
|
// This DLL has been seen in several crash reports. Some Googling
|
||||||
// suggests it's highly unstable and causes issues in many games.
|
// suggests it's highly unstable and causes issues in many games.
|
||||||
L"EZFRD32.dll",
|
L"EZFRD32.dll",
|
||||||
L"EZFRD64.dll",
|
L"EZFRD64.dll",
|
||||||
|
|
||||||
|
// These are the dList DLLs for Optimus hybrid graphics DDI.
|
||||||
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
|
||||||
|
//
|
||||||
|
// We forcefully block them from loading because Optimus has a bug that
|
||||||
|
// deadlocks DXVA2 when we present with D3DPRESENT_DONOTWAIT. This will prevent
|
||||||
|
// Optimus from ever using the dGPU even if the user has requested it.
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/240
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/235
|
||||||
|
L"nvdlist.dll",
|
||||||
|
L"nvdlistx.dll",
|
||||||
|
|
||||||
|
// In some unknown circumstances, RTSS tries to hook in the middle of an instruction, leaving garbage
|
||||||
|
// code inside d3d9.dll that causes a crash when executed:
|
||||||
|
//
|
||||||
|
// 0:000> u
|
||||||
|
// d3d9!D3D9GetCurrentOwnershipMode+0x5d:
|
||||||
|
// 00007ff8`95b95861 9b wait
|
||||||
|
// 00007ff8`95b95862 a7 cmps dword ptr [rsi],dword ptr [rdi] <--- crash happens here
|
||||||
|
// 00007ff8`95b95863 ff ???
|
||||||
|
// 00007ff8`95b95864 bfe8ca8a00 mov edi,8ACAE8h
|
||||||
|
// 00007ff8`95b95869 00eb add bl,ch
|
||||||
|
// 00007ff8`95b9586b f1 ???
|
||||||
|
// 00007ff8`95b9586c b808000000 mov eax,8
|
||||||
|
// 00007ff8`95b95871 ebe6 jmp d3d9!D3D9GetCurrentOwnershipMode+0x55 (00007ff8`95b95859)
|
||||||
|
//
|
||||||
|
// Disassembling starting at the exact address of the attempted hook yields the intended jmp instruction
|
||||||
|
//
|
||||||
|
// 0:000> u d3d9!D3D9GetCurrentOwnershipMode+0x5c:
|
||||||
|
// 00007ff8`95b95860 e99ba7ffbf jmp 00007ff8`55b90000
|
||||||
|
//
|
||||||
|
// Since the RTSS OSD doesn't even work with DXVA2, we'll just block the hooks entirely.
|
||||||
|
L"RTSSHooks.dll",
|
||||||
|
L"RTSSHooks64.dll",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ You can follow development on our [Discord server](https://discord.gg/6ERtzFY).
|
|||||||
|
|
||||||
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
||||||
[](https://travis-ci.org/moonlight-stream/moonlight-qt)
|
[](https://travis-ci.org/moonlight-stream/moonlight-qt)
|
||||||
|
[](https://github.com/moonlight-stream/moonlight-qt/releases)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Hardware accelerated video decoding on Windows, Mac, and Linux
|
- Hardware accelerated video decoding on Windows, Mac, and Linux
|
||||||
|
|||||||
+1
-1
Submodule app/SDL_GameControllerDB updated: d57f564649...aeebc86f9f.
+23
-4
@@ -84,6 +84,9 @@ unix:!macx {
|
|||||||
packagesExist(libva-wayland) {
|
packagesExist(libva-wayland) {
|
||||||
CONFIG += libva-wayland
|
CONFIG += libva-wayland
|
||||||
}
|
}
|
||||||
|
packagesExist(libva-drm) {
|
||||||
|
CONFIG += libva-drm
|
||||||
|
}
|
||||||
CONFIG += libva
|
CONFIG += libva
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,16 +110,16 @@ win32 {
|
|||||||
CONFIG += ffmpeg
|
CONFIG += ffmpeg
|
||||||
}
|
}
|
||||||
win32:!winrt {
|
win32:!winrt {
|
||||||
CONFIG += soundio
|
CONFIG += soundio discord-rpc
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
|
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
|
||||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit
|
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal
|
||||||
|
|
||||||
# For libsoundio
|
# For libsoundio
|
||||||
LIBS += -framework CoreAudio -framework AudioUnit
|
LIBS += -framework CoreAudio -framework AudioUnit
|
||||||
|
|
||||||
CONFIG += ffmpeg soundio
|
CONFIG += ffmpeg soundio discord-rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
@@ -128,6 +131,7 @@ SOURCES += \
|
|||||||
backend/nvpairingmanager.cpp \
|
backend/nvpairingmanager.cpp \
|
||||||
backend/computermanager.cpp \
|
backend/computermanager.cpp \
|
||||||
backend/boxartmanager.cpp \
|
backend/boxartmanager.cpp \
|
||||||
|
backend/richpresencemanager.cpp \
|
||||||
cli/commandlineparser.cpp \
|
cli/commandlineparser.cpp \
|
||||||
cli/quitstream.cpp \
|
cli/quitstream.cpp \
|
||||||
cli/startstream.cpp \
|
cli/startstream.cpp \
|
||||||
@@ -155,6 +159,7 @@ HEADERS += \
|
|||||||
backend/nvpairingmanager.h \
|
backend/nvpairingmanager.h \
|
||||||
backend/computermanager.h \
|
backend/computermanager.h \
|
||||||
backend/boxartmanager.h \
|
backend/boxartmanager.h \
|
||||||
|
backend/richpresencemanager.h \
|
||||||
cli/commandlineparser.h \
|
cli/commandlineparser.h \
|
||||||
cli/quitstream.h \
|
cli/quitstream.h \
|
||||||
cli/startstream.h \
|
cli/startstream.h \
|
||||||
@@ -182,6 +187,7 @@ ffmpeg {
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
streaming/video/ffmpeg.cpp \
|
streaming/video/ffmpeg.cpp \
|
||||||
streaming/video/ffmpeg-renderers/sdlvid.cpp \
|
streaming/video/ffmpeg-renderers/sdlvid.cpp \
|
||||||
|
streaming/video/ffmpeg-renderers/cuda.cpp \
|
||||||
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
|
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
|
||||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
|
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
|
||||||
|
|
||||||
@@ -189,6 +195,7 @@ ffmpeg {
|
|||||||
streaming/video/ffmpeg.h \
|
streaming/video/ffmpeg.h \
|
||||||
streaming/video/ffmpeg-renderers/renderer.h \
|
streaming/video/ffmpeg-renderers/renderer.h \
|
||||||
streaming/video/ffmpeg-renderers/sdlvid.h \
|
streaming/video/ffmpeg-renderers/sdlvid.h \
|
||||||
|
streaming/video/ffmpeg-renderers/cuda.h \
|
||||||
streaming/video/ffmpeg-renderers/pacer/pacer.h \
|
streaming/video/ffmpeg-renderers/pacer/pacer.h \
|
||||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
|
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
|
||||||
}
|
}
|
||||||
@@ -212,6 +219,12 @@ libva-wayland {
|
|||||||
PKGCONFIG += libva-wayland
|
PKGCONFIG += libva-wayland
|
||||||
DEFINES += HAVE_LIBVA_WAYLAND
|
DEFINES += HAVE_LIBVA_WAYLAND
|
||||||
}
|
}
|
||||||
|
libva-wayland {
|
||||||
|
message(VAAPI DRM support enabled)
|
||||||
|
|
||||||
|
PKGCONFIG += libva-drm
|
||||||
|
DEFINES += HAVE_LIBVA_DRM
|
||||||
|
}
|
||||||
libvdpau {
|
libvdpau {
|
||||||
message(VDPAU renderer selected)
|
message(VDPAU renderer selected)
|
||||||
|
|
||||||
@@ -273,6 +286,12 @@ soundio {
|
|||||||
SOURCES += streaming/audio/renderers/soundioaudiorenderer.cpp
|
SOURCES += streaming/audio/renderers/soundioaudiorenderer.cpp
|
||||||
HEADERS += streaming/audio/renderers/soundioaudiorenderer.h
|
HEADERS += streaming/audio/renderers/soundioaudiorenderer.h
|
||||||
}
|
}
|
||||||
|
discord-rpc {
|
||||||
|
message(Discord integration enabled)
|
||||||
|
|
||||||
|
LIBS += -ldiscord-rpc
|
||||||
|
DEFINES += HAVE_DISCORD
|
||||||
|
}
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
resources.qrc \
|
resources.qrc \
|
||||||
@@ -365,7 +384,7 @@ macx {
|
|||||||
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
APP_BUNDLE_RESOURCES.files = moonlight.icns
|
||||||
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
APP_BUNDLE_RESOURCES.path = Contents/Resources
|
||||||
|
|
||||||
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true)
|
APP_BUNDLE_FRAMEWORKS.files = $$files(../libs/mac/Frameworks/*.framework, true) $$files(../libs/mac/lib/*.dylib, true)
|
||||||
APP_BUNDLE_FRAMEWORKS.path = Contents/Frameworks
|
APP_BUNDLE_FRAMEWORKS.path = Contents/Frameworks
|
||||||
|
|
||||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES APP_BUNDLE_FRAMEWORKS
|
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES APP_BUNDLE_FRAMEWORKS
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ AutoUpdateChecker::AutoUpdateChecker(QObject *parent) :
|
|||||||
|
|
||||||
void AutoUpdateChecker::start()
|
void AutoUpdateChecker::start()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) // Only run update checker on platforms without auto-update
|
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN) || defined(STEAM_LINK) // Only run update checker on platforms without auto-update
|
||||||
|
// HACK: Set network accessibility to work around QTBUG-80947
|
||||||
|
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
|
||||||
|
|
||||||
// We'll get a callback when this is finished
|
// We'll get a callback when this is finished
|
||||||
QUrl url("https://moonlight-stream.org/updates/qt.json");
|
QUrl url("https://moonlight-stream.org/updates/qt.json");
|
||||||
m_Nam.get(QNetworkRequest(url));
|
m_Nam.get(QNetworkRequest(url));
|
||||||
@@ -43,6 +46,15 @@ void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString AutoUpdateChecker::getPlatform()
|
||||||
|
{
|
||||||
|
#ifdef STEAM_LINK
|
||||||
|
return QStringLiteral("steamlink");
|
||||||
|
#else
|
||||||
|
return QSysInfo::productType();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
||||||
{
|
{
|
||||||
Q_ASSERT(reply->isFinished());
|
Q_ASSERT(reply->isFinished());
|
||||||
@@ -88,7 +100,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
|
if (updateObj["arch"] == QSysInfo::buildCpuArchitecture() &&
|
||||||
updateObj["platform"] == QSysInfo::productType()) {
|
updateObj["platform"] == getPlatform()) {
|
||||||
qDebug() << "Found update manifest match for current platform";
|
qDebug() << "Found update manifest match for current platform";
|
||||||
|
|
||||||
QString latestVersion = updateObj["version"].toString();
|
QString latestVersion = updateObj["version"].toString();
|
||||||
@@ -137,7 +149,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qWarning() << "No entry in update manifest found for current platform: "
|
qWarning() << "No entry in update manifest found for current platform: "
|
||||||
<< QSysInfo::buildCpuArchitecture() << QSysInfo::productType();
|
<< QSysInfo::buildCpuArchitecture() << getPlatform();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qWarning() << "Update checking failed with error: " << reply->error();
|
qWarning() << "Update checking failed with error: " << reply->error();
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
void parseStringToVersionQuad(QString& string, QVector<int>& version);
|
void parseStringToVersionQuad(QString& string, QVector<int>& version);
|
||||||
|
|
||||||
|
QString getPlatform();
|
||||||
|
|
||||||
QVector<int> m_CurrentVersionQuad;
|
QVector<int> m_CurrentVersionQuad;
|
||||||
QNetworkAccessManager m_Nam;
|
QNetworkAccessManager m_Nam;
|
||||||
};
|
};
|
||||||
+104
-21
@@ -216,6 +216,42 @@ void ComputerManager::saveHosts()
|
|||||||
settings.endArray();
|
settings.endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QHostAddress ComputerManager::getBestGlobalAddressV6(QVector<QHostAddress> &addresses)
|
||||||
|
{
|
||||||
|
for (const QHostAddress& address : addresses) {
|
||||||
|
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
if (address.isInSubnet(QHostAddress("fe80::"), 10)) {
|
||||||
|
// Link-local
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address.isInSubnet(QHostAddress("fec0::"), 10)) {
|
||||||
|
qInfo() << "Ignoring site-local address:" << address;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address.isInSubnet(QHostAddress("fc00::"), 7)) {
|
||||||
|
qInfo() << "Ignoring ULA:" << address;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address.isInSubnet(QHostAddress("2002::"), 16)) {
|
||||||
|
qInfo() << "Ignoring 6to4 address:" << address;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address.isInSubnet(QHostAddress("2001::"), 32)) {
|
||||||
|
qInfo() << "Ignoring Teredo address:" << address;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QHostAddress();
|
||||||
|
}
|
||||||
|
|
||||||
void ComputerManager::startPolling()
|
void ComputerManager::startPolling()
|
||||||
{
|
{
|
||||||
QWriteLocker lock(&m_Lock);
|
QWriteLocker lock(&m_Lock);
|
||||||
@@ -233,9 +269,9 @@ void ComputerManager::startPolling()
|
|||||||
this, [this](const QMdnsEngine::Service& service) {
|
this, [this](const QMdnsEngine::Service& service) {
|
||||||
qInfo() << "Discovered mDNS host:" << service.hostname();
|
qInfo() << "Discovered mDNS host:" << service.hostname();
|
||||||
|
|
||||||
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, &m_MdnsCache, service);
|
MdnsPendingComputer* pendingComputer = new MdnsPendingComputer(&m_MdnsServer, service);
|
||||||
connect(pendingComputer, SIGNAL(resolvedv4(MdnsPendingComputer*,QHostAddress)),
|
connect(pendingComputer, &MdnsPendingComputer::resolvedHost,
|
||||||
this, SLOT(handleMdnsServiceResolved(MdnsPendingComputer*,QHostAddress)));
|
this, &ComputerManager::handleMdnsServiceResolved);
|
||||||
m_PendingResolution.append(pendingComputer);
|
m_PendingResolution.append(pendingComputer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -277,11 +313,39 @@ void ComputerManager::startPollingComputer(NvComputer* computer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
||||||
const QHostAddress& address)
|
QVector<QHostAddress>& addresses)
|
||||||
{
|
{
|
||||||
qInfo() << "Resolved" << computer->hostname() << "to" << address.toString();
|
QHostAddress v6Global = getBestGlobalAddressV6(addresses);
|
||||||
|
bool added = false;
|
||||||
|
|
||||||
addNewHost(address.toString(), true);
|
// Add the host using the IPv4 address
|
||||||
|
for (const QHostAddress& address : addresses) {
|
||||||
|
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
// NB: We don't just call addNewHost() here with v6Global because the IPv6
|
||||||
|
// address may not be reachable (if the user hasn't installed the IPv6 helper yet
|
||||||
|
// or if this host lacks outbound IPv6 capability). We want to add IPv6 even if
|
||||||
|
// it's not currently reachable.
|
||||||
|
addNewHost(address.toString(), true, v6Global);
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!added) {
|
||||||
|
// If we get here, there wasn't an IPv4 address so we'll do it v6-only
|
||||||
|
for (const QHostAddress& address : addresses) {
|
||||||
|
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
// Use a link-local or site-local address for the "local address"
|
||||||
|
if (address.isInSubnet(QHostAddress("fe80::"), 10) ||
|
||||||
|
address.isInSubnet(QHostAddress("fec0::"), 10) ||
|
||||||
|
address.isInSubnet(QHostAddress("fc00::"), 7)) {
|
||||||
|
addNewHost(address.toString(), true, v6Global);
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_PendingResolution.removeOne(computer);
|
m_PendingResolution.removeOne(computer);
|
||||||
computer->deleteLater();
|
computer->deleteLater();
|
||||||
@@ -508,9 +572,10 @@ class PendingAddTask : public QObject, public QRunnable
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PendingAddTask(ComputerManager* computerManager, QString address, bool mdns)
|
PendingAddTask(ComputerManager* computerManager, QString address, QHostAddress mdnsIpv6Address, bool mdns)
|
||||||
: m_ComputerManager(computerManager),
|
: m_ComputerManager(computerManager),
|
||||||
m_Address(address),
|
m_Address(address),
|
||||||
|
m_MdnsIpv6Address(mdnsIpv6Address),
|
||||||
m_Mdns(mdns)
|
m_Mdns(mdns)
|
||||||
{
|
{
|
||||||
connect(this, &PendingAddTask::computerAddCompleted,
|
connect(this, &PendingAddTask::computerAddCompleted,
|
||||||
@@ -560,16 +625,21 @@ private:
|
|||||||
{
|
{
|
||||||
NvHTTP http(m_Address, QSslCertificate());
|
NvHTTP http(m_Address, QSslCertificate());
|
||||||
|
|
||||||
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user");
|
qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user") << m_MdnsIpv6Address;
|
||||||
|
|
||||||
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
|
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
|
||||||
QString serverInfo = fetchServerInfo(http);
|
QString serverInfo = fetchServerInfo(http);
|
||||||
|
if (serverInfo.isEmpty() && !m_MdnsIpv6Address.isNull()) {
|
||||||
|
// Retry using the global IPv6 address if the IPv4 or link-local IPv6 address fails
|
||||||
|
http.setAddress(m_MdnsIpv6Address.toString());
|
||||||
|
serverInfo = fetchServerInfo(http);
|
||||||
|
}
|
||||||
if (serverInfo.isEmpty()) {
|
if (serverInfo.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create initial newComputer using HTTP serverinfo with no pinned cert
|
// Create initial newComputer using HTTP serverinfo with no pinned cert
|
||||||
NvComputer* newComputer = new NvComputer(m_Address, serverInfo, QSslCertificate());
|
NvComputer* newComputer = new NvComputer(http.address(), serverInfo, QSslCertificate());
|
||||||
|
|
||||||
// Check if we have a record of this host UUID to pull the pinned cert
|
// Check if we have a record of this host UUID to pull the pinned cert
|
||||||
NvComputer* existingComputer;
|
NvComputer* existingComputer;
|
||||||
@@ -589,22 +659,34 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the polled computer with the HTTPS information
|
// Update the polled computer with the HTTPS information
|
||||||
NvComputer httpsComputer(m_Address, serverInfo, QSslCertificate());
|
NvComputer httpsComputer(http.address(), serverInfo, QSslCertificate());
|
||||||
newComputer->update(httpsComputer);
|
newComputer->update(httpsComputer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update addresses depending on the context
|
// Update addresses depending on the context
|
||||||
if (m_Mdns) {
|
if (m_Mdns) {
|
||||||
newComputer->localAddress = m_Address;
|
// Only update local address if we actually reached the PC via this address.
|
||||||
|
// If we reached it via the IPv6 address after the local address failed,
|
||||||
// Get the WAN IP address using STUN if we're on mDNS
|
// don't store the non-working local address.
|
||||||
quint32 addr;
|
if (http.address() == m_Address) {
|
||||||
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
newComputer->localAddress = m_Address;
|
||||||
if (err == 0) {
|
|
||||||
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
qWarning() << "STUN failed to get WAN address:" << err;
|
// Get the WAN IP address using STUN if we're on mDNS over IPv4
|
||||||
|
if (QHostAddress(newComputer->localAddress).protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
quint32 addr;
|
||||||
|
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
||||||
|
if (err == 0) {
|
||||||
|
newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "STUN failed to get WAN address:" << err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_MdnsIpv6Address.isNull()) {
|
||||||
|
Q_ASSERT(m_MdnsIpv6Address.protocol() == QAbstractSocket::IPv6Protocol);
|
||||||
|
newComputer->ipv6Address = m_MdnsIpv6Address.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -657,14 +739,15 @@ private:
|
|||||||
|
|
||||||
ComputerManager* m_ComputerManager;
|
ComputerManager* m_ComputerManager;
|
||||||
QString m_Address;
|
QString m_Address;
|
||||||
|
QHostAddress m_MdnsIpv6Address;
|
||||||
bool m_Mdns;
|
bool m_Mdns;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ComputerManager::addNewHost(QString address, bool mdns)
|
void ComputerManager::addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address)
|
||||||
{
|
{
|
||||||
// Punt to a worker thread to avoid stalling the
|
// Punt to a worker thread to avoid stalling the
|
||||||
// UI while waiting for serverinfo query to complete
|
// UI while waiting for serverinfo query to complete
|
||||||
PendingAddTask* addTask = new PendingAddTask(this, address, mdns);
|
PendingAddTask* addTask = new PendingAddTask(this, address, mdnsIpv6Address, mdns);
|
||||||
QThreadPool::globalInstance()->start(addTask);
|
QThreadPool::globalInstance()->start(addTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <QReadWriteLock>
|
#include <QReadWriteLock>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QRunnable>
|
#include <QRunnable>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
class MdnsPendingComputer : public QObject
|
class MdnsPendingComputer : public QObject
|
||||||
{
|
{
|
||||||
@@ -20,13 +21,18 @@ class MdnsPendingComputer : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MdnsPendingComputer(QMdnsEngine::Server* server,
|
explicit MdnsPendingComputer(QMdnsEngine::Server* server,
|
||||||
QMdnsEngine::Cache* cache,
|
|
||||||
const QMdnsEngine::Service& service)
|
const QMdnsEngine::Service& service)
|
||||||
: m_Hostname(service.hostname()),
|
: m_Hostname(service.hostname()),
|
||||||
m_Resolver(server, m_Hostname, cache)
|
m_Server(server),
|
||||||
|
m_Resolver(nullptr)
|
||||||
{
|
{
|
||||||
connect(&m_Resolver, SIGNAL(resolved(QHostAddress)),
|
// Start resolving
|
||||||
this, SLOT(handleResolved(QHostAddress)));
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~MdnsPendingComputer()
|
||||||
|
{
|
||||||
|
delete m_Resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString hostname()
|
QString hostname()
|
||||||
@@ -35,20 +41,42 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleResolved(const QHostAddress& address)
|
void handleResolvedTimeout()
|
||||||
{
|
{
|
||||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
if (m_Addresses.isEmpty()) {
|
||||||
m_Resolver.disconnect();
|
// Try again
|
||||||
emit resolvedv4(this, address);
|
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Q_ASSERT(!m_Addresses.isEmpty());
|
||||||
|
emit resolvedHost(this, m_Addresses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleResolvedAddress(const QHostAddress& address)
|
||||||
|
{
|
||||||
|
qInfo() << "Resolved" << hostname() << "to" << address;
|
||||||
|
m_Addresses.push_back(address);
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void resolvedv4(MdnsPendingComputer*, const QHostAddress&);
|
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void resolve()
|
||||||
|
{
|
||||||
|
delete m_Resolver;
|
||||||
|
m_Resolver = new QMdnsEngine::Resolver(m_Server, m_Hostname);
|
||||||
|
connect(m_Resolver, &QMdnsEngine::Resolver::resolved,
|
||||||
|
this, &MdnsPendingComputer::handleResolvedAddress);
|
||||||
|
QTimer::singleShot(2000, this, SLOT(handleResolvedTimeout()));
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray m_Hostname;
|
QByteArray m_Hostname;
|
||||||
QMdnsEngine::Resolver m_Resolver;
|
QMdnsEngine::Server* m_Server;
|
||||||
|
QMdnsEngine::Resolver* m_Resolver;
|
||||||
|
QVector<QHostAddress> m_Addresses;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ComputerPollingEntry
|
class ComputerPollingEntry
|
||||||
@@ -140,7 +168,7 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void stopPollingAsync();
|
Q_INVOKABLE void stopPollingAsync();
|
||||||
|
|
||||||
Q_INVOKABLE void addNewHost(QString address, bool mdns);
|
Q_INVOKABLE void addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address = QHostAddress());
|
||||||
|
|
||||||
void pairHost(NvComputer* computer, QString pin);
|
void pairHost(NvComputer* computer, QString pin);
|
||||||
|
|
||||||
@@ -165,11 +193,13 @@ private slots:
|
|||||||
|
|
||||||
void handleComputerStateChanged(NvComputer* computer);
|
void handleComputerStateChanged(NvComputer* computer);
|
||||||
|
|
||||||
void handleMdnsServiceResolved(MdnsPendingComputer* computer, const QHostAddress& address);
|
void handleMdnsServiceResolved(MdnsPendingComputer* computer, QVector<QHostAddress>& addresses);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void saveHosts();
|
void saveHosts();
|
||||||
|
|
||||||
|
QHostAddress getBestGlobalAddressV6(QVector<QHostAddress>& addresses);
|
||||||
|
|
||||||
void startPollingComputer(NvComputer* computer);
|
void startPollingComputer(NvComputer* computer);
|
||||||
|
|
||||||
int m_PollingRef;
|
int m_PollingRef;
|
||||||
|
|||||||
@@ -39,11 +39,18 @@ void ComputerSeeker::onComputerUpdated(NvComputer *computer)
|
|||||||
bool ComputerSeeker::matchComputer(NvComputer *computer) const
|
bool ComputerSeeker::matchComputer(NvComputer *computer) const
|
||||||
{
|
{
|
||||||
QString value = m_ComputerName.toLower();
|
QString value = m_ComputerName.toLower();
|
||||||
return computer->name.toLower() == value ||
|
|
||||||
computer->localAddress.toLower() == value ||
|
if (computer->name.toLower() == value || computer->uuid.toLower() == value) {
|
||||||
computer->remoteAddress.toLower() == value ||
|
return true;
|
||||||
computer->manualAddress.toLower() == value ||
|
}
|
||||||
computer->uuid.toLower() == value;
|
|
||||||
|
for (const QString& addr : computer->uniqueAddresses()) {
|
||||||
|
if (addr.toLower() == value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ComputerSeeker::isOnline(NvComputer *computer) const
|
bool ComputerSeeker::isOnline(NvComputer *computer) const
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ void IdentityManager::createCredentials(QSettings& settings)
|
|||||||
-1, -1, 0);
|
-1, -1, 0);
|
||||||
X509_set_issuer_name(cert, name);
|
X509_set_issuer_name(cert, name);
|
||||||
|
|
||||||
X509_sign(cert, pk, EVP_sha1());
|
X509_sign(cert, pk, EVP_sha256());
|
||||||
|
|
||||||
BIO* biokey = BIO_new(BIO_s_mem());
|
BIO* biokey = BIO_new(BIO_s_mem());
|
||||||
THROW_BAD_ALLOC_IF_NULL(biokey);
|
THROW_BAD_ALLOC_IF_NULL(biokey);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QUdpSocket>
|
#include <QUdpSocket>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
|
#include <QNetworkProxy>
|
||||||
|
|
||||||
#define SER_NAME "hostname"
|
#define SER_NAME "hostname"
|
||||||
#define SER_UUID "uuid"
|
#define SER_UUID "uuid"
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#define SER_LOCALADDR "localaddress"
|
#define SER_LOCALADDR "localaddress"
|
||||||
#define SER_REMOTEADDR "remoteaddress"
|
#define SER_REMOTEADDR "remoteaddress"
|
||||||
#define SER_MANUALADDR "manualaddress"
|
#define SER_MANUALADDR "manualaddress"
|
||||||
|
#define SER_IPV6ADDR "ipv6address"
|
||||||
#define SER_APPLIST "apps"
|
#define SER_APPLIST "apps"
|
||||||
#define SER_SRVCERT "srvcert"
|
#define SER_SRVCERT "srvcert"
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ NvComputer::NvComputer(QSettings& settings)
|
|||||||
this->macAddress = settings.value(SER_MAC).toByteArray();
|
this->macAddress = settings.value(SER_MAC).toByteArray();
|
||||||
this->localAddress = settings.value(SER_LOCALADDR).toString();
|
this->localAddress = settings.value(SER_LOCALADDR).toString();
|
||||||
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
|
this->remoteAddress = settings.value(SER_REMOTEADDR).toString();
|
||||||
|
this->ipv6Address = settings.value(SER_IPV6ADDR).toString();
|
||||||
this->manualAddress = settings.value(SER_MANUALADDR).toString();
|
this->manualAddress = settings.value(SER_MANUALADDR).toString();
|
||||||
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
|
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
|
||||||
|
|
||||||
@@ -63,6 +66,7 @@ void NvComputer::serialize(QSettings& settings)
|
|||||||
settings.setValue(SER_MAC, macAddress);
|
settings.setValue(SER_MAC, macAddress);
|
||||||
settings.setValue(SER_LOCALADDR, localAddress);
|
settings.setValue(SER_LOCALADDR, localAddress);
|
||||||
settings.setValue(SER_REMOTEADDR, remoteAddress);
|
settings.setValue(SER_REMOTEADDR, remoteAddress);
|
||||||
|
settings.setValue(SER_IPV6ADDR, ipv6Address);
|
||||||
settings.setValue(SER_MANUALADDR, manualAddress);
|
settings.setValue(SER_MANUALADDR, manualAddress);
|
||||||
settings.setValue(SER_SRVCERT, serverCert.toPem());
|
settings.setValue(SER_SRVCERT, serverCert.toPem());
|
||||||
|
|
||||||
@@ -129,7 +133,12 @@ NvComputer::NvComputer(QString address, QString serverInfo, QSslCertificate serv
|
|||||||
mode2.width * mode2.height * mode2.refreshRate;
|
mode2.width * mode2.height * mode2.refreshRate;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// We can get an IPv4 loopback address if we're using the GS IPv6 Forwarder
|
||||||
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
|
this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP");
|
||||||
|
if (this->localAddress.startsWith("127.")) {
|
||||||
|
this->localAddress = QString();
|
||||||
|
}
|
||||||
|
|
||||||
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
|
this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP");
|
||||||
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
||||||
PS_PAIRED : PS_NOT_PAIRED;
|
PS_PAIRED : PS_NOT_PAIRED;
|
||||||
@@ -229,6 +238,81 @@ bool NvComputer::wake()
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NvComputer::isReachableOverVpn()
|
||||||
|
{
|
||||||
|
if (activeAddress.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTcpSocket s;
|
||||||
|
|
||||||
|
s.setProxy(QNetworkProxy::NoProxy);
|
||||||
|
s.connectToHost(activeAddress, 47984);
|
||||||
|
if (s.waitForConnected(3000)) {
|
||||||
|
Q_ASSERT(!s.localAddress().isNull());
|
||||||
|
|
||||||
|
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
||||||
|
// Ensure the interface is up
|
||||||
|
if ((nic.flags() & QNetworkInterface::IsUp) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QNetworkAddressEntry& addr : nic.addressEntries()) {
|
||||||
|
if (addr.ip() == s.localAddress()) {
|
||||||
|
qInfo() << "Found matching interface:" << nic.humanReadableName() << nic.hardwareAddress() << nic.flags();
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
||||||
|
qInfo() << "Interface Type:" << nic.type();
|
||||||
|
qInfo() << "Interface MTU:" << nic.maximumTransmissionUnit();
|
||||||
|
|
||||||
|
if (nic.type() == QNetworkInterface::Virtual ||
|
||||||
|
nic.type() == QNetworkInterface::Ppp) {
|
||||||
|
// Treat PPP and virtual interfaces as likely VPNs
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nic.maximumTransmissionUnit() != 0 && nic.maximumTransmissionUnit() < 1500) {
|
||||||
|
// Treat MTUs under 1500 as likely VPNs
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (nic.flags() & QNetworkInterface::IsPointToPoint) {
|
||||||
|
// Treat point-to-point links as likely VPNs.
|
||||||
|
// This check detects OpenVPN on Unix-like OSes.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
||||||
|
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nic.humanReadableName().startsWith("ZeroTier")) {
|
||||||
|
// ZeroTier interfaces always start with "ZeroTier"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nic.humanReadableName().contains("VPN")) {
|
||||||
|
// This one is just a final heuristic if all else fails
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Didn't meet any of our VPN heuristics
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "No match found for address:" << s.localAddress();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If we fail to connect, just pretend that it's not a VPN
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QVector<QString> NvComputer::uniqueAddresses()
|
QVector<QString> NvComputer::uniqueAddresses()
|
||||||
{
|
{
|
||||||
QVector<QString> uniqueAddressList;
|
QVector<QString> uniqueAddressList;
|
||||||
@@ -237,6 +321,7 @@ QVector<QString> NvComputer::uniqueAddresses()
|
|||||||
uniqueAddressList.append(activeAddress);
|
uniqueAddressList.append(activeAddress);
|
||||||
uniqueAddressList.append(localAddress);
|
uniqueAddressList.append(localAddress);
|
||||||
uniqueAddressList.append(remoteAddress);
|
uniqueAddressList.append(remoteAddress);
|
||||||
|
uniqueAddressList.append(ipv6Address);
|
||||||
uniqueAddressList.append(manualAddress);
|
uniqueAddressList.append(manualAddress);
|
||||||
|
|
||||||
// Prune duplicates (always giving precedence to the first)
|
// Prune duplicates (always giving precedence to the first)
|
||||||
@@ -296,6 +381,7 @@ bool NvComputer::update(NvComputer& that)
|
|||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress);
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress);
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress);
|
||||||
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(ipv6Address);
|
||||||
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
|
ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress);
|
||||||
ASSIGN_IF_CHANGED(pairState);
|
ASSIGN_IF_CHANGED(pairState);
|
||||||
ASSIGN_IF_CHANGED(serverCodecModeSupport);
|
ASSIGN_IF_CHANGED(serverCodecModeSupport);
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ public:
|
|||||||
bool
|
bool
|
||||||
wake();
|
wake();
|
||||||
|
|
||||||
|
bool
|
||||||
|
isReachableOverVpn();
|
||||||
|
|
||||||
QVector<QString>
|
QVector<QString>
|
||||||
uniqueAddresses();
|
uniqueAddresses();
|
||||||
|
|
||||||
@@ -64,6 +67,7 @@ public:
|
|||||||
// Persisted traits
|
// Persisted traits
|
||||||
QString localAddress;
|
QString localAddress;
|
||||||
QString remoteAddress;
|
QString remoteAddress;
|
||||||
|
QString ipv6Address;
|
||||||
QString manualAddress;
|
QString manualAddress;
|
||||||
QByteArray macAddress;
|
QByteArray macAddress;
|
||||||
QString name;
|
QString name;
|
||||||
|
|||||||
+20
-5
@@ -18,18 +18,15 @@
|
|||||||
#define QUIT_TIMEOUT_MS 30000
|
#define QUIT_TIMEOUT_MS 30000
|
||||||
|
|
||||||
NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
|
NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) :
|
||||||
m_Address(address),
|
|
||||||
m_ServerCert(serverCert)
|
m_ServerCert(serverCert)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!address.isEmpty());
|
|
||||||
|
|
||||||
m_BaseUrlHttp.setScheme("http");
|
m_BaseUrlHttp.setScheme("http");
|
||||||
m_BaseUrlHttps.setScheme("https");
|
m_BaseUrlHttps.setScheme("https");
|
||||||
m_BaseUrlHttp.setHost(address);
|
|
||||||
m_BaseUrlHttps.setHost(address);
|
|
||||||
m_BaseUrlHttp.setPort(47989);
|
m_BaseUrlHttp.setPort(47989);
|
||||||
m_BaseUrlHttps.setPort(47984);
|
m_BaseUrlHttps.setPort(47984);
|
||||||
|
|
||||||
|
setAddress(address);
|
||||||
|
|
||||||
// Never use a proxy server
|
// Never use a proxy server
|
||||||
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
||||||
m_Nam.setProxy(noProxy);
|
m_Nam.setProxy(noProxy);
|
||||||
@@ -42,6 +39,21 @@ void NvHTTP::setServerCert(QSslCertificate serverCert)
|
|||||||
m_ServerCert = serverCert;
|
m_ServerCert = serverCert;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NvHTTP::setAddress(QString address)
|
||||||
|
{
|
||||||
|
Q_ASSERT(!address.isEmpty());
|
||||||
|
|
||||||
|
m_Address = address;
|
||||||
|
|
||||||
|
m_BaseUrlHttp.setHost(address);
|
||||||
|
m_BaseUrlHttps.setHost(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NvHTTP::address()
|
||||||
|
{
|
||||||
|
return m_Address;
|
||||||
|
}
|
||||||
|
|
||||||
QVector<int>
|
QVector<int>
|
||||||
NvHTTP::parseQuad(QString quad)
|
NvHTTP::parseQuad(QString quad)
|
||||||
{
|
{
|
||||||
@@ -442,6 +454,9 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
// Add our client certificate
|
// Add our client certificate
|
||||||
request.setSslConfiguration(IdentityManager::get()->getSslConfig());
|
request.setSslConfiguration(IdentityManager::get()->getSslConfig());
|
||||||
|
|
||||||
|
// HACK: Set reachability to work around QTBUG-80947
|
||||||
|
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
|
||||||
|
|
||||||
QNetworkReply* reply = m_Nam.get(request);
|
QNetworkReply* reply = m_Nam.get(request);
|
||||||
|
|
||||||
// Run the request with a timeout if requested
|
// Run the request with a timeout if requested
|
||||||
|
|||||||
@@ -156,6 +156,10 @@ public:
|
|||||||
|
|
||||||
void setServerCert(QSslCertificate serverCert);
|
void setServerCert(QSslCertificate serverCert);
|
||||||
|
|
||||||
|
void setAddress(QString address);
|
||||||
|
|
||||||
|
QString address();
|
||||||
|
|
||||||
static
|
static
|
||||||
QVector<int>
|
QVector<int>
|
||||||
parseQuad(QString quad);
|
parseQuad(QString quad);
|
||||||
|
|||||||
@@ -256,7 +256,9 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
|||||||
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
||||||
challengeResponse.append(clientSecretData);
|
challengeResponse.append(clientSecretData);
|
||||||
|
|
||||||
QByteArray encryptedChallengeResponseHash = encrypt(QCryptographicHash::hash(challengeResponse, hashAlgo), &encKey);
|
QByteArray paddedHash = QCryptographicHash::hash(challengeResponse, hashAlgo);
|
||||||
|
paddedHash.resize(32);
|
||||||
|
QByteArray encryptedChallengeResponseHash = encrypt(paddedHash, &encKey);
|
||||||
QString respXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
|
QString respXml = m_Http.openConnectionToString(m_Http.m_BaseUrlHttp,
|
||||||
"pair",
|
"pair",
|
||||||
"devicename=roth&updateState=1&serverchallengeresp=" +
|
"devicename=roth&updateState=1&serverchallengeresp=" +
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#include "richpresencemanager.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
RichPresenceManager::RichPresenceManager(StreamingPreferences& prefs, QString gameName)
|
||||||
|
: m_DiscordActive(false)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
if (prefs.richPresence) {
|
||||||
|
DiscordEventHandlers handlers = {};
|
||||||
|
handlers.ready = discordReady;
|
||||||
|
handlers.disconnected = discordDisconnected;
|
||||||
|
handlers.errored = discordErrored;
|
||||||
|
Discord_Initialize("594668102021677159", &handlers, 0, nullptr);
|
||||||
|
m_DiscordActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DiscordActive) {
|
||||||
|
QByteArray stateStr = (QString("Streaming ") + gameName).toUtf8();
|
||||||
|
|
||||||
|
DiscordRichPresence discordPresence = {};
|
||||||
|
discordPresence.state = stateStr.data();
|
||||||
|
discordPresence.startTimestamp = time(nullptr);
|
||||||
|
discordPresence.largeImageKey = "icon";
|
||||||
|
Discord_UpdatePresence(&discordPresence);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
RichPresenceManager::~RichPresenceManager()
|
||||||
|
{
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
if (m_DiscordActive) {
|
||||||
|
Discord_ClearPresence();
|
||||||
|
Discord_Shutdown();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresenceManager::runCallbacks()
|
||||||
|
{
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
if (m_DiscordActive) {
|
||||||
|
Discord_RunCallbacks();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
void RichPresenceManager::discordReady(const DiscordUser* request)
|
||||||
|
{
|
||||||
|
qInfo() << "Discord integration ready for user:" << request->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresenceManager::discordDisconnected(int errorCode, const char *message)
|
||||||
|
{
|
||||||
|
qInfo() << "Discord integration disconnected:" << errorCode << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresenceManager::discordErrored(int errorCode, const char *message)
|
||||||
|
{
|
||||||
|
qWarning() << "Discord integration error:" << errorCode << message;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "settings/streamingpreferences.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
#include <discord_rpc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class RichPresenceManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RichPresenceManager(StreamingPreferences& prefs, QString gameName);
|
||||||
|
~RichPresenceManager();
|
||||||
|
|
||||||
|
void runCallbacks();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
static void discordReady(const DiscordUser* request);
|
||||||
|
static void discordDisconnected(int errorCode, const char* message);
|
||||||
|
static void discordErrored(int errorCode, const char* message);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool m_DiscordActive;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -22,6 +22,12 @@ SystemProperties::SystemProperties()
|
|||||||
hasBrowser = false;
|
hasBrowser = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_DISCORD
|
||||||
|
hasDiscordIntegration = true;
|
||||||
|
#else
|
||||||
|
hasDiscordIntegration = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
|
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
|
||||||
|
|
||||||
// Populate data that requires talking to SDL. We do it all in one shot
|
// Populate data that requires talking to SDL. We do it all in one shot
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public:
|
|||||||
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
|
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
|
||||||
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
|
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
|
||||||
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
|
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
|
||||||
|
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
|
||||||
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
|
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
|
||||||
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
|
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ private:
|
|||||||
bool isRunningXWayland;
|
bool isRunningXWayland;
|
||||||
bool isWow64;
|
bool isWow64;
|
||||||
bool hasBrowser;
|
bool hasBrowser;
|
||||||
|
bool hasDiscordIntegration;
|
||||||
QString unmappedGamepads;
|
QString unmappedGamepads;
|
||||||
int maximumStreamingFrameRate;
|
int maximumStreamingFrameRate;
|
||||||
QList<QRect> monitorDesktopResolutions;
|
QList<QRect> monitorDesktopResolutions;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>Streaming at up to 4K resolution</li>
|
<li>Streaming at up to 4K resolution</li>
|
||||||
<li>Support for up to 120 FPS streaming (high refresh rate monitor recommended)</li>
|
<li>Support for up to 120 FPS streaming (high refresh rate monitor recommended)</li>
|
||||||
<li>Hardware accelerated video decoding with VAAPI and VDPAU support</li>
|
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
|
||||||
<li>5.1 surround sound audio support</li>
|
<li>5.1 surround sound audio support</li>
|
||||||
<li>HEVC support for better video compression efficiency</li>
|
<li>HEVC support for better video compression efficiency</li>
|
||||||
<li>Gamepad support with force feedback for up to 4 players</li>
|
<li>Gamepad support with force feedback for up to 4 players</li>
|
||||||
@@ -33,6 +33,60 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.2.0" date="2019-12-17">
|
||||||
|
<description>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved video encoding efficiency</li>
|
||||||
|
<li>Improved performance when streaming over a VPN</li>
|
||||||
|
<li>Fixed RTSP handshake error on certain networks</li>
|
||||||
|
<li>Fixed rumble not working on certain gamepads</li>
|
||||||
|
<li>Fixed pairing error when double-clicking on an unpaired PC</li>
|
||||||
|
<li>Updated included SDL gamepad mappings</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="1.1.1" date="2019-09-29">
|
||||||
|
<description>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved reliability of public IP address detection</li>
|
||||||
|
<li>Fixed connecting to a host PC with multiple network connections</li>
|
||||||
|
<li>Fixed a hang after exiting a full-screen stream with GNOME on Wayland</li>
|
||||||
|
<li>Fixed gamepad hotplugging</li>
|
||||||
|
<li>Updated to SDL 2.0.10</li>
|
||||||
|
<li>Updated to Qt 5.13.1</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="1.1.0" date="2019-08-05">
|
||||||
|
<description>
|
||||||
|
<p>New features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Added support for NVDEC for NVIDIA GPUs on Wayland</li>
|
||||||
|
<li>Added an option to disable gamepad mouse mode for games that require holding the Start button</li>
|
||||||
|
<li>Added automatic detection of IPv6 addresses for Internet streaming over IPv6 networks</li>
|
||||||
|
</ul>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved speed and reliability of automatic PC detection on the local network</li>
|
||||||
|
<li>Fixed audio latency increasing under certain network conditions</li>
|
||||||
|
<li>Fixed 5.1 surround sound sometimes causing no audio to play when streaming over the Internet</li>
|
||||||
|
<li>Fixed graceful termination of games not being detected properly on GFE 3.19</li>
|
||||||
|
<li>Fixed several audio issues on Linux by switching back to SDL for audio</li>
|
||||||
|
<li>Changed client certificate signature algorithm from SHA-1 to SHA-256 for Debian Buster compatibility</li>
|
||||||
|
<li>Updated included gamepad mappings</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.1" date="2019-05-27">
|
||||||
|
<description>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed broken video on GeForce Experience 3.19</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="1.0.0" date="2019-05-21">
|
<release version="1.0.0" date="2019-05-21">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ CenteredGridView {
|
|||||||
text: model.name
|
text: model.name
|
||||||
width: appIcon.width
|
width: appIcon.width
|
||||||
height: model.running ? 175 : appIcon.height
|
height: model.running ? 175 : appIcon.height
|
||||||
|
leftPadding: 20
|
||||||
|
rightPadding: 20
|
||||||
anchors.left: appIcon.left
|
anchors.left: appIcon.left
|
||||||
anchors.right: appIcon.right
|
anchors.right: appIcon.right
|
||||||
anchors.bottom: appIcon.bottom
|
anchors.bottom: appIcon.bottom
|
||||||
|
|||||||
+9
-3
@@ -104,7 +104,7 @@ CenteredGridView {
|
|||||||
Image {
|
Image {
|
||||||
id: pcIcon
|
id: pcIcon
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
source: "qrc:/res/ic_tv_white_48px.svg"
|
source: "qrc:/res/desktop_windows-48px.svg"
|
||||||
sourceSize {
|
sourceSize {
|
||||||
width: 200
|
width: 200
|
||||||
height: 200
|
height: 200
|
||||||
@@ -116,7 +116,7 @@ CenteredGridView {
|
|||||||
id: stateIcon
|
id: stateIcon
|
||||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||||
anchors.verticalCenter: pcIcon.verticalCenter
|
anchors.verticalCenter: pcIcon.verticalCenter
|
||||||
anchors.verticalCenterOffset: -10
|
anchors.verticalCenterOffset: -15
|
||||||
visible: !model.statusUnknown && (!model.online || !model.paired)
|
visible: !model.statusUnknown && (!model.online || !model.paired)
|
||||||
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
source: !model.online ? "qrc:/res/baseline-warning-24px.svg" : "qrc:/res/baseline-lock-24px.svg"
|
||||||
sourceSize {
|
sourceSize {
|
||||||
@@ -129,7 +129,7 @@ CenteredGridView {
|
|||||||
id: statusUnknownSpinner
|
id: statusUnknownSpinner
|
||||||
anchors.horizontalCenter: pcIcon.horizontalCenter
|
anchors.horizontalCenter: pcIcon.horizontalCenter
|
||||||
anchors.verticalCenter: pcIcon.verticalCenter
|
anchors.verticalCenter: pcIcon.verticalCenter
|
||||||
anchors.verticalCenterOffset: -10
|
anchors.verticalCenterOffset: -15
|
||||||
width: 75
|
width: 75
|
||||||
height: 75
|
height: 75
|
||||||
visible: model.statusUnknown
|
visible: model.statusUnknown
|
||||||
@@ -235,6 +235,12 @@ CenteredGridView {
|
|||||||
|
|
||||||
NavigableMessageDialog {
|
NavigableMessageDialog {
|
||||||
id: pairDialog
|
id: pairDialog
|
||||||
|
|
||||||
|
// Pairing dialog must be modal to prevent double-clicks from triggering
|
||||||
|
// pairing twice
|
||||||
|
modal: true
|
||||||
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
|
||||||
// don't allow edits to the rest of the window while open
|
// don't allow edits to the rest of the window while open
|
||||||
property string pin : "0000"
|
property string pin : "0000"
|
||||||
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed."
|
text:"Please enter " + pin + " on your GameStream PC. This dialog will close when pairing is completed."
|
||||||
|
|||||||
@@ -328,6 +328,16 @@ Flickable {
|
|||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
// Set the recommended option based on the OS
|
||||||
|
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||||
|
var thisWm = windowModeListModel.get(i).val;
|
||||||
|
if (thisWm === StreamingPreferences.recommendedFullScreenMode) {
|
||||||
|
windowModeListModel.get(i).text += " (Recommended)"
|
||||||
|
windowModeListModel.move(i, 0, 1);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var savedWm = StreamingPreferences.windowMode
|
var savedWm = StreamingPreferences.windowMode
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
for (var i = 0; i < windowModeListModel.count; i++) {
|
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||||
@@ -346,7 +356,7 @@ Flickable {
|
|||||||
model: ListModel {
|
model: ListModel {
|
||||||
id: windowModeListModel
|
id: windowModeListModel
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Full-screen (Recommended)"
|
text: "Full-screen"
|
||||||
val: StreamingPreferences.WM_FULLSCREEN
|
val: StreamingPreferences.WM_FULLSCREEN
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
@@ -489,6 +499,22 @@ Flickable {
|
|||||||
StreamingPreferences.connectionWarnings = checked
|
StreamingPreferences.connectionWarnings = checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
visible: SystemProperties.hasDiscordIntegration
|
||||||
|
id: discordPresenceCheck
|
||||||
|
text: "Discord Rich Presence integration"
|
||||||
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.richPresence
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.richPresence = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "Updates your Discord status to display the name of the game you're streaming."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -541,6 +567,22 @@ Flickable {
|
|||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: "When checked, mouse input is not accelerated or scaled by the OS before passing to Moonlight"
|
ToolTip.text: "When checked, mouse input is not accelerated or scaled by the OS before passing to Moonlight"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: gamepadMouseCheck
|
||||||
|
hoverEnabled: true
|
||||||
|
text: "Gamepad mouse mode support"
|
||||||
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.gamepadMouse
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.gamepadMouse = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 3000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: "When enabled, holding the Start button will toggle mouse mode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -239,13 +239,17 @@ ApplicationWindow {
|
|||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 3000
|
ToolTip.timeout: 3000
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered || visible
|
||||||
|
|
||||||
// Invisible until we get a callback notifying us that
|
// Invisible until we get a callback notifying us that
|
||||||
// an update is available
|
// an update is available
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onClicked: Qt.openUrlExternally(browserUrl);
|
onClicked: {
|
||||||
|
if (SystemProperties.hasBrowser) {
|
||||||
|
Qt.openUrlExternally(browserUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateAvailable(version, url)
|
function updateAvailable(version, url)
|
||||||
{
|
{
|
||||||
|
|||||||
+20
-1
@@ -9,6 +9,7 @@
|
|||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
// Don't let SDL hook our main function, since Qt is already
|
// Don't let SDL hook our main function, since Qt is already
|
||||||
// doing the same thing. This needs to be before any headers
|
// doing the same thing. This needs to be before any headers
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
#ifdef USE_CUSTOM_LOGGER
|
||||||
static QTime s_LoggerTime;
|
static QElapsedTimer s_LoggerTime;
|
||||||
static QTextStream s_LoggerStream(stdout);
|
static QTextStream s_LoggerStream(stdout);
|
||||||
static QMutex s_LoggerLock;
|
static QMutex s_LoggerLock;
|
||||||
#ifdef LOG_TO_FILE
|
#ifdef LOG_TO_FILE
|
||||||
@@ -284,8 +285,14 @@ int main(int argc, char *argv[])
|
|||||||
AntiHookingDummyImport();
|
AntiHookingDummyImport();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Enable High DPI support
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
// Enable fractional High DPI scaling on Qt 5.14 and later
|
||||||
|
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||||
|
#endif
|
||||||
|
|
||||||
// This avoids using the default keychain for SSL, which may cause
|
// This avoids using the default keychain for SSL, which may cause
|
||||||
// password prompts on macOS.
|
// password prompts on macOS.
|
||||||
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
|
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
|
||||||
@@ -309,6 +316,18 @@ int main(int argc, char *argv[])
|
|||||||
// Register custom metatypes for use in signals
|
// Register custom metatypes for use in signals
|
||||||
qRegisterMetaType<NvApp>("NvApp");
|
qRegisterMetaType<NvApp>("NvApp");
|
||||||
|
|
||||||
|
SDL_version compileVersion;
|
||||||
|
SDL_VERSION(&compileVersion);
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Compiled with SDL %d.%d.%d",
|
||||||
|
compileVersion.major, compileVersion.minor, compileVersion.patch);
|
||||||
|
|
||||||
|
SDL_version runtimeVersion;
|
||||||
|
SDL_GetVersion(&runtimeVersion);
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Running with SDL %d.%d.%d",
|
||||||
|
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
|
||||||
|
|
||||||
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
|
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
|
||||||
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
|
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
|
||||||
// initializing the SDL video subsystem to have any effect.
|
// initializing the SDL video subsystem to have any effect.
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF" width="48" height="48" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" d="M0 0h24v24H0V0z"/>
|
||||||
|
<path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
|
||||||
<path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 303 B |
@@ -1,9 +0,0 @@
|
|||||||
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs>
|
|
||||||
<path d="M0 0h24v24H0V0z" id="a"/>
|
|
||||||
</defs>
|
|
||||||
<clipPath id="b">
|
|
||||||
<use overflow="visible" xlink:href="#a"/>
|
|
||||||
</clipPath>
|
|
||||||
<path clip-path="url(#b)" d="M21 3H3c-1.11 0-2 .89-2 2v12c0 1.1.89 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.11-.9-2-2-2zm0 14H3V5h18v12zm-5-7v2H8v-2h8z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 467 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg fill="#FFFFFF" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
|
||||||
<path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 273 B |
+1
-3
@@ -1,9 +1,7 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>res/ic_remove_circle_outline_white_48px.svg</file>
|
|
||||||
<file>res/ic_remove_from_queue_white_48px.svg</file>
|
|
||||||
<file>res/ic_videogame_asset_white_48px.svg</file>
|
<file>res/ic_videogame_asset_white_48px.svg</file>
|
||||||
<file>res/ic_tv_white_48px.svg</file>
|
<file>res/desktop_windows-48px.svg</file>
|
||||||
<file>res/ic_add_to_queue_white_48px.svg</file>
|
<file>res/ic_add_to_queue_white_48px.svg</file>
|
||||||
<file>res/baseline-lock-24px.svg</file>
|
<file>res/baseline-lock-24px.svg</file>
|
||||||
<file>res/baseline-play_circle_filled_white-48px.svg</file>
|
<file>res/baseline-play_circle_filled_white-48px.svg</file>
|
||||||
|
|||||||
@@ -23,6 +23,11 @@
|
|||||||
#define SER_STARTWINDOWED "startwindowed"
|
#define SER_STARTWINDOWED "startwindowed"
|
||||||
#define SER_FRAMEPACING "framepacing"
|
#define SER_FRAMEPACING "framepacing"
|
||||||
#define SER_CONNWARNINGS "connwarnings"
|
#define SER_CONNWARNINGS "connwarnings"
|
||||||
|
#define SER_RICHPRESENCE "richpresence"
|
||||||
|
#define SER_GAMEPADMOUSE "gamepadmouse"
|
||||||
|
#define SER_DEFAULTVER "defaultver"
|
||||||
|
|
||||||
|
#define CURRENT_DEFAULT_VER 1
|
||||||
|
|
||||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
StreamingPreferences::StreamingPreferences(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -34,6 +39,14 @@ void StreamingPreferences::reload()
|
|||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
|
int defaultVer = settings.value(SER_DEFAULTVER, 0).toInt();
|
||||||
|
|
||||||
|
#ifdef Q_OS_DARWIN
|
||||||
|
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
||||||
|
#else
|
||||||
|
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN;
|
||||||
|
#endif
|
||||||
|
|
||||||
width = settings.value(SER_WIDTH, 1280).toInt();
|
width = settings.value(SER_WIDTH, 1280).toInt();
|
||||||
height = settings.value(SER_HEIGHT, 720).toInt();
|
height = settings.value(SER_HEIGHT, 720).toInt();
|
||||||
fps = settings.value(SER_FPS, 60).toInt();
|
fps = settings.value(SER_FPS, 60).toInt();
|
||||||
@@ -49,6 +62,8 @@ void StreamingPreferences::reload()
|
|||||||
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
|
||||||
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
|
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
|
||||||
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
|
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
|
||||||
|
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
|
||||||
|
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
|
||||||
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
|
||||||
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
|
static_cast<int>(AudioConfig::AC_STEREO)).toInt());
|
||||||
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
|
||||||
@@ -58,7 +73,17 @@ void StreamingPreferences::reload()
|
|||||||
windowMode = static_cast<WindowMode>(settings.value(SER_WINDOWMODE,
|
windowMode = static_cast<WindowMode>(settings.value(SER_WINDOWMODE,
|
||||||
// Try to load from the old preference value too
|
// Try to load from the old preference value too
|
||||||
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
|
static_cast<int>(settings.value(SER_FULLSCREEN, true).toBool() ?
|
||||||
WindowMode::WM_FULLSCREEN : WindowMode::WM_WINDOWED)).toInt());
|
recommendedFullScreenMode : WindowMode::WM_WINDOWED)).toInt());
|
||||||
|
|
||||||
|
// Perform default settings updates as required based on last default version
|
||||||
|
if (defaultVer == 0) {
|
||||||
|
#ifdef Q_OS_DARWIN
|
||||||
|
// Update window mode setting on macOS from full-screen (old default) to borderless windowed (new default)
|
||||||
|
if (windowMode == WindowMode::WM_FULLSCREEN) {
|
||||||
|
windowMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamingPreferences::save()
|
void StreamingPreferences::save()
|
||||||
@@ -80,10 +105,13 @@ void StreamingPreferences::save()
|
|||||||
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
settings.setValue(SER_STARTWINDOWED, startWindowed);
|
||||||
settings.setValue(SER_FRAMEPACING, framePacing);
|
settings.setValue(SER_FRAMEPACING, framePacing);
|
||||||
settings.setValue(SER_CONNWARNINGS, connectionWarnings);
|
settings.setValue(SER_CONNWARNINGS, connectionWarnings);
|
||||||
|
settings.setValue(SER_RICHPRESENCE, richPresence);
|
||||||
|
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
|
||||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||||
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));
|
||||||
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
settings.setValue(SER_WINDOWMODE, static_cast<int>(windowMode));
|
||||||
|
settings.setValue(SER_DEFAULTVER, CURRENT_DEFAULT_VER);
|
||||||
}
|
}
|
||||||
|
|
||||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||||
|
|||||||
@@ -64,10 +64,13 @@ public:
|
|||||||
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
|
||||||
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
|
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
|
||||||
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
|
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
|
||||||
|
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
|
||||||
|
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
|
||||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||||
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
|
||||||
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
|
Q_PROPERTY(WindowMode windowMode MEMBER windowMode NOTIFY windowModeChanged)
|
||||||
|
Q_PROPERTY(WindowMode recommendedFullScreenMode MEMBER recommendedFullScreenMode CONSTANT)
|
||||||
|
|
||||||
// Directly accessible members for preferences
|
// Directly accessible members for preferences
|
||||||
int width;
|
int width;
|
||||||
@@ -85,10 +88,13 @@ public:
|
|||||||
bool startWindowed;
|
bool startWindowed;
|
||||||
bool framePacing;
|
bool framePacing;
|
||||||
bool connectionWarnings;
|
bool connectionWarnings;
|
||||||
|
bool richPresence;
|
||||||
|
bool gamepadMouse;
|
||||||
AudioConfig audioConfig;
|
AudioConfig audioConfig;
|
||||||
VideoCodecConfig videoCodecConfig;
|
VideoCodecConfig videoCodecConfig;
|
||||||
VideoDecoderSelection videoDecoderSelection;
|
VideoDecoderSelection videoDecoderSelection;
|
||||||
WindowMode windowMode;
|
WindowMode windowMode;
|
||||||
|
WindowMode recommendedFullScreenMode;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void displayModeChanged();
|
void displayModeChanged();
|
||||||
@@ -108,5 +114,7 @@ signals:
|
|||||||
void startWindowedChanged();
|
void startWindowedChanged();
|
||||||
void framePacingChanged();
|
void framePacingChanged();
|
||||||
void connectionWarningsChanged();
|
void connectionWarningsChanged();
|
||||||
|
void richPresenceChanged();
|
||||||
|
void gamepadMouseChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
+110
-36
@@ -13,30 +13,101 @@
|
|||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
IAudioRenderer* Session::createAudioRenderer()
|
#define TRY_INIT_RENDERER(renderer, opusConfig) \
|
||||||
|
{ \
|
||||||
|
IAudioRenderer* __renderer = new renderer(); \
|
||||||
|
if (__renderer->prepareForPlayback(opusConfig)) \
|
||||||
|
return __renderer; \
|
||||||
|
delete __renderer; \
|
||||||
|
}
|
||||||
|
|
||||||
|
IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig)
|
||||||
{
|
{
|
||||||
#if defined(HAVE_SOUNDIO)
|
// Handle explicit ML_AUDIO setting and fail if the requested backend fails
|
||||||
if (qgetenv("ML_AUDIO") == "SDL") {
|
QString mlAudio = qgetenv("ML_AUDIO").toLower();
|
||||||
return new SdlAudioRenderer();
|
if (mlAudio == "sdl") {
|
||||||
|
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#ifdef HAVE_SOUNDIO
|
||||||
|
else if (mlAudio == "libsoundio") {
|
||||||
|
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(HAVE_SLAUDIO)
|
||||||
|
else if (mlAudio == "slaudio") {
|
||||||
|
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (!mlAudio.isEmpty()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unknown audio backend: %s",
|
||||||
|
SDL_getenv("ML_AUDIO"));
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SoundIoAudioRenderer();
|
// -------------- Automatic backend selection below this line ---------------
|
||||||
#elif defined(HAVE_SLAUDIO)
|
|
||||||
return new SLAudioRenderer();
|
#if defined(HAVE_SLAUDIO)
|
||||||
#else
|
// Steam Link should always have SLAudio
|
||||||
return new SdlAudioRenderer();
|
TRY_INIT_RENDERER(SLAudioRenderer, opusConfig)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(Q_OS_WIN32) && !defined(Q_OS_DARWIN)
|
||||||
|
// Linux defaults to SDL due to persistent glitching issues under libsoundio.
|
||||||
|
// Platforms that libsoundio doesn't support also default to SDL.
|
||||||
|
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
|
||||||
|
#ifdef HAVE_SOUNDIO
|
||||||
|
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
// Windows and macOS default to libsoundio and fall back to SDL
|
||||||
|
#ifdef HAVE_SOUNDIO
|
||||||
|
TRY_INIT_RENDERER(SoundIoAudioRenderer, opusConfig)
|
||||||
|
#endif
|
||||||
|
TRY_INIT_RENDERER(SdlAudioRenderer, opusConfig)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Session::getAudioRendererCapabilities(int audioConfiguration)
|
||||||
|
{
|
||||||
|
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
||||||
|
// the renderer the channel count and sample rate.
|
||||||
|
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
|
||||||
|
opusConfig.sampleRate = 48000;
|
||||||
|
opusConfig.samplesPerFrame = 240;
|
||||||
|
|
||||||
|
switch (audioConfiguration)
|
||||||
|
{
|
||||||
|
case AUDIO_CONFIGURATION_STEREO:
|
||||||
|
opusConfig.channelCount = 2;
|
||||||
|
break;
|
||||||
|
case AUDIO_CONFIGURATION_51_SURROUND:
|
||||||
|
opusConfig.channelCount = 6;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
IAudioRenderer* audioRenderer = createAudioRenderer(&opusConfig);
|
||||||
|
if (audioRenderer == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int caps = audioRenderer->getCapabilities();
|
||||||
|
|
||||||
|
delete audioRenderer;
|
||||||
|
|
||||||
|
return caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::testAudio(int audioConfiguration)
|
bool Session::testAudio(int audioConfiguration)
|
||||||
{
|
{
|
||||||
IAudioRenderer* audioRenderer;
|
|
||||||
|
|
||||||
audioRenderer = createAudioRenderer();
|
|
||||||
if (audioRenderer == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
||||||
// the renderer the channel count and sample rate.
|
// the renderer the channel count and sample rate.
|
||||||
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
|
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
|
||||||
@@ -56,11 +127,14 @@ bool Session::testAudio(int audioConfiguration)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ret = audioRenderer->prepareForPlayback(&opusConfig);
|
IAudioRenderer* audioRenderer = createAudioRenderer(&opusConfig);
|
||||||
|
if (audioRenderer == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
delete audioRenderer;
|
delete audioRenderer;
|
||||||
|
|
||||||
return ret;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Session::arInit(int /* audioConfiguration */,
|
int Session::arInit(int /* audioConfiguration */,
|
||||||
@@ -71,30 +145,34 @@ int Session::arInit(int /* audioConfiguration */,
|
|||||||
|
|
||||||
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
||||||
|
|
||||||
|
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
||||||
|
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
|
||||||
|
s_ActiveSession->m_AudioRenderer->remapChannels(&s_ActiveSession->m_AudioConfig);
|
||||||
|
|
||||||
|
// Create the Opus decoder with the renderer's preferred channel mapping
|
||||||
s_ActiveSession->m_OpusDecoder =
|
s_ActiveSession->m_OpusDecoder =
|
||||||
opus_multistream_decoder_create(opusConfig->sampleRate,
|
opus_multistream_decoder_create(s_ActiveSession->m_AudioConfig.sampleRate,
|
||||||
opusConfig->channelCount,
|
s_ActiveSession->m_AudioConfig.channelCount,
|
||||||
opusConfig->streams,
|
s_ActiveSession->m_AudioConfig.streams,
|
||||||
opusConfig->coupledStreams,
|
s_ActiveSession->m_AudioConfig.coupledStreams,
|
||||||
opusConfig->mapping,
|
s_ActiveSession->m_AudioConfig.mapping,
|
||||||
&error);
|
&error);
|
||||||
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
||||||
|
delete s_ActiveSession->m_AudioRenderer;
|
||||||
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to create decoder: %d",
|
"Failed to create decoder: %d",
|
||||||
error);
|
error);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
|
||||||
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(opusConfig)) {
|
|
||||||
delete s_ActiveSession->m_AudioRenderer;
|
|
||||||
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Audio stream has %d channels",
|
"Audio stream has %d channels",
|
||||||
opusConfig->channelCount);
|
s_ActiveSession->m_AudioConfig.channelCount);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -185,11 +263,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
// so we return to real-time playback and don't accumulate latency.
|
// so we return to real-time playback and don't accumulate latency.
|
||||||
Uint32 audioReinitStartTime = SDL_GetTicks();
|
Uint32 audioReinitStartTime = SDL_GetTicks();
|
||||||
|
|
||||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer();
|
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
||||||
if (!s_ActiveSession->m_AudioRenderer->prepareForPlayback(&s_ActiveSession->m_AudioConfig)) {
|
|
||||||
delete s_ActiveSession->m_AudioRenderer;
|
|
||||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint32 audioReinitStopTime = SDL_GetTicks();
|
Uint32 audioReinitStopTime = SDL_GetTicks();
|
||||||
|
|
||||||
|
|||||||
@@ -13,4 +13,16 @@ public:
|
|||||||
|
|
||||||
// Return false if an unrecoverable error has occurred and the renderer must be reinitialized
|
// Return false if an unrecoverable error has occurred and the renderer must be reinitialized
|
||||||
virtual bool submitAudio(int bytesWritten) = 0;
|
virtual bool submitAudio(int bytesWritten) = 0;
|
||||||
|
|
||||||
|
virtual int getCapabilities() = 0;
|
||||||
|
|
||||||
|
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION) {
|
||||||
|
// Use default channel mapping:
|
||||||
|
// 0 - Front Left
|
||||||
|
// 1 - Front Right
|
||||||
|
// 2 - Center
|
||||||
|
// 3 - LFE
|
||||||
|
// 4 - Surround Left
|
||||||
|
// 5 - Surround Right
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -16,7 +16,10 @@ public:
|
|||||||
|
|
||||||
virtual bool submitAudio(int bytesWritten);
|
virtual bool submitAudio(int bytesWritten);
|
||||||
|
|
||||||
|
virtual int getCapabilities();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_AudioDeviceID m_AudioDevice;
|
SDL_AudioDeviceID m_AudioDevice;
|
||||||
void* m_AudioBuffer;
|
void* m_AudioBuffer;
|
||||||
|
int m_FrameSize;
|
||||||
};
|
};
|
||||||
@@ -33,6 +33,8 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
|||||||
// Specifying non-Po2 seems to work for our supported platforms.
|
// Specifying non-Po2 seems to work for our supported platforms.
|
||||||
want.samples = opusConfig->samplesPerFrame;
|
want.samples = opusConfig->samplesPerFrame;
|
||||||
|
|
||||||
|
m_FrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||||
|
|
||||||
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
m_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||||
if (m_AudioDevice == 0) {
|
if (m_AudioDevice == 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -41,7 +43,7 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_AudioBuffer = malloc(opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount);
|
m_AudioBuffer = malloc(m_FrameSize);
|
||||||
if (m_AudioBuffer == nullptr) {
|
if (m_AudioBuffer == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to allocate audio buffer");
|
"Failed to allocate audio buffer");
|
||||||
@@ -92,6 +94,18 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't queue if there's already more than 30 ms of audio data waiting
|
||||||
|
// in Moonlight's audio queue.
|
||||||
|
if (LiGetPendingAudioDuration() > 30) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide backpressure on the queue to ensure too many frames don't build up
|
||||||
|
// in SDL's audio queue.
|
||||||
|
while (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize > 10) {
|
||||||
|
SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (SDL_QueueAudio(m_AudioDevice, m_AudioBuffer, bytesWritten) < 0) {
|
if (SDL_QueueAudio(m_AudioDevice, m_AudioBuffer, bytesWritten) < 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to queue audio sample: %s",
|
"Failed to queue audio sample: %s",
|
||||||
@@ -100,3 +114,9 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SdlAudioRenderer::getCapabilities()
|
||||||
|
{
|
||||||
|
// Direct submit can't be used because we use LiGetPendingAudioDuration()
|
||||||
|
return CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
|
|||||||
// it's hard to avoid since we get crushed by CPU limitations.
|
// it's hard to avoid since we get crushed by CPU limitations.
|
||||||
m_MaxQueuedAudioMs = 40 * opusConfig->channelCount / 2;
|
m_MaxQueuedAudioMs = 40 * opusConfig->channelCount / 2;
|
||||||
|
|
||||||
m_FrameDuration = opusConfig->samplesPerFrame / 48;
|
|
||||||
m_AudioBufferSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
m_AudioBufferSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||||
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
|
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
|
||||||
opusConfig->sampleRate,
|
opusConfig->sampleRate,
|
||||||
@@ -37,12 +36,35 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Using SLAudio renderer with %d ms frames",
|
"Using SLAudio renderer with %d samples per frame",
|
||||||
m_FrameDuration);
|
opusConfig->samplesPerFrame);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SWAP_CHANNEL(i, j) \
|
||||||
|
tmp = opusConfig->mapping[i]; \
|
||||||
|
opusConfig->mapping[i] = opusConfig->mapping[j]; \
|
||||||
|
opusConfig->mapping[j] = tmp
|
||||||
|
|
||||||
|
void SLAudioRenderer::remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
|
||||||
|
unsigned char tmp;
|
||||||
|
|
||||||
|
if (opusConfig->channelCount == 6) {
|
||||||
|
// The Moonlight's default channel order is FL,FR,C,LFE,RL,RR
|
||||||
|
// SLAudio expects FL,C,FR,RL,RR,LFE so we swap the channels around to match
|
||||||
|
|
||||||
|
// Swap FR and C - now FL,C,FR,LFE,RL,RR
|
||||||
|
SWAP_CHANNEL(1, 2);
|
||||||
|
|
||||||
|
// Swap LFE and RR - now FL,C,FR,RR,RL,LFE
|
||||||
|
SWAP_CHANNEL(3, 5);
|
||||||
|
|
||||||
|
// Swap RR and RL - now FL,C,FR,RL,RR,LFE
|
||||||
|
SWAP_CHANNEL(3, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void* SLAudioRenderer::getAudioBuffer(int* size)
|
void* SLAudioRenderer::getAudioBuffer(int* size)
|
||||||
{
|
{
|
||||||
SDL_assert(*size == m_AudioBufferSize);
|
SDL_assert(*size == m_AudioBufferSize);
|
||||||
@@ -77,7 +99,7 @@ bool SLAudioRenderer::submitAudio(int bytesWritten)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LiGetPendingAudioFrames() * m_FrameDuration < m_MaxQueuedAudioMs) {
|
if (LiGetPendingAudioDuration() < m_MaxQueuedAudioMs) {
|
||||||
SLAudio_SubmitFrame(m_AudioStream);
|
SLAudio_SubmitFrame(m_AudioStream);
|
||||||
m_AudioBuffer = nullptr;
|
m_AudioBuffer = nullptr;
|
||||||
}
|
}
|
||||||
@@ -90,6 +112,11 @@ bool SLAudioRenderer::submitAudio(int bytesWritten)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SLAudioRenderer::getCapabilities()
|
||||||
|
{
|
||||||
|
return CAPABILITY_SLOW_OPUS_DECODER | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
void SLAudioRenderer::slLogCallback(void*, ESLAudioLog logLevel, const char *message)
|
void SLAudioRenderer::slLogCallback(void*, ESLAudioLog logLevel, const char *message)
|
||||||
{
|
{
|
||||||
SDL_LogPriority priority;
|
SDL_LogPriority priority;
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ public:
|
|||||||
|
|
||||||
virtual bool submitAudio(int bytesWritten);
|
virtual bool submitAudio(int bytesWritten);
|
||||||
|
|
||||||
|
virtual int getCapabilities();
|
||||||
|
|
||||||
|
virtual void remapChannels(POPUS_MULTISTREAM_CONFIGURATION opusConfig);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
|
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
|
||||||
|
|
||||||
@@ -24,6 +28,5 @@ private:
|
|||||||
|
|
||||||
void* m_AudioBuffer;
|
void* m_AudioBuffer;
|
||||||
int m_AudioBufferSize;
|
int m_AudioBufferSize;
|
||||||
int m_FrameDuration;
|
|
||||||
int m_MaxQueuedAudioMs;
|
int m_MaxQueuedAudioMs;
|
||||||
};
|
};
|
||||||
@@ -4,29 +4,13 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
// GFE sends us packets in 5 ms chunks
|
|
||||||
const double SoundIoAudioRenderer::k_RawSampleLengthSec = 0.005;
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
// PulseAudio and ALSA require more than just 5 ms samples
|
|
||||||
// for some reason, so write a minimum of 25 ms each time to
|
|
||||||
// prevent underruns on Bluetooth.
|
|
||||||
// https://github.com/moonlight-stream/moonlight-qt/issues/147
|
|
||||||
// https://github.com/moonlight-stream/moonlight-qt/issues/157
|
|
||||||
const double SoundIoAudioRenderer::k_MinSampleLengthSec = 0.025;
|
|
||||||
#else
|
|
||||||
// This determines the size of the buffers we'll
|
|
||||||
// get from CoreAudio. It is also the minimum
|
|
||||||
// size that we will write when called to fill a buffer.
|
|
||||||
const double SoundIoAudioRenderer::k_MinSampleLengthSec = k_RawSampleLengthSec;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SoundIoAudioRenderer::SoundIoAudioRenderer()
|
SoundIoAudioRenderer::SoundIoAudioRenderer()
|
||||||
: m_OpusChannelCount(0),
|
: m_OpusChannelCount(0),
|
||||||
m_SoundIo(nullptr),
|
m_SoundIo(nullptr),
|
||||||
m_Device(nullptr),
|
m_Device(nullptr),
|
||||||
m_OutputStream(nullptr),
|
m_OutputStream(nullptr),
|
||||||
m_RingBuffer(nullptr),
|
m_RingBuffer(nullptr),
|
||||||
|
m_AudioPacketDuration(0),
|
||||||
m_Latency(0),
|
m_Latency(0),
|
||||||
m_Errored(false)
|
m_Errored(false)
|
||||||
{
|
{
|
||||||
@@ -172,9 +156,11 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_AudioPacketDuration = (opusConfig->samplesPerFrame / (opusConfig->sampleRate / 1000)) / 1000.0;
|
||||||
|
|
||||||
m_OutputStream->format = SoundIoFormatS16NE;
|
m_OutputStream->format = SoundIoFormatS16NE;
|
||||||
m_OutputStream->sample_rate = opusConfig->sampleRate;
|
m_OutputStream->sample_rate = opusConfig->sampleRate;
|
||||||
m_OutputStream->software_latency = k_MinSampleLengthSec;
|
m_OutputStream->software_latency = m_AudioPacketDuration;
|
||||||
m_OutputStream->name = "Moonlight";
|
m_OutputStream->name = "Moonlight";
|
||||||
m_OutputStream->userdata = this;
|
m_OutputStream->userdata = this;
|
||||||
m_OutputStream->error_callback = sioErrorCallback;
|
m_OutputStream->error_callback = sioErrorCallback;
|
||||||
@@ -237,25 +223,21 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
|||||||
|
|
||||||
int packetsToBuffer;
|
int packetsToBuffer;
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
// PulseAudio and ALSA need the large buffer (see comment on k_MinSampleLengthSec),
|
|
||||||
// so we need a buffer at least double that size to allow packets to arrive
|
|
||||||
// while we're writing to the sink.
|
|
||||||
packetsToBuffer = (int)(k_MinSampleLengthSec / k_RawSampleLengthSec) * 2;
|
|
||||||
#else
|
|
||||||
if (m_SoundIo->current_backend == SoundIoBackendWasapi) {
|
if (m_SoundIo->current_backend == SoundIoBackendWasapi) {
|
||||||
// 15 ms buffer seems to be fine for WASAPI
|
// 15 ms buffer seems to be fine for WASAPI
|
||||||
packetsToBuffer = 3;
|
packetsToBuffer = (int)ceil(0.015 / m_AudioPacketDuration);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// 30 ms buffer on CoreAudio to avoid glitching on macOS
|
// 30 ms buffer on CoreAudio to avoid glitching on macOS
|
||||||
packetsToBuffer = 6;
|
packetsToBuffer = (int)ceil(0.030 / m_AudioPacketDuration);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
// Always buffer at least 2 packets
|
||||||
|
packetsToBuffer = qMax(2, packetsToBuffer);
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Audio buffer size: %d packets",
|
"Audio buffer size: %f seconds",
|
||||||
packetsToBuffer);
|
packetsToBuffer * m_AudioPacketDuration);
|
||||||
|
|
||||||
m_RingBuffer = soundio_ring_buffer_create(m_SoundIo,
|
m_RingBuffer = soundio_ring_buffer_create(m_SoundIo,
|
||||||
m_OutputStream->bytes_per_sample *
|
m_OutputStream->bytes_per_sample *
|
||||||
@@ -317,6 +299,12 @@ bool SoundIoAudioRenderer::submitAudio(int bytesWritten)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SoundIoAudioRenderer::getCapabilities()
|
||||||
|
{
|
||||||
|
// TODO: Tweak buffer sizes then re-enable arbitrary audio duration
|
||||||
|
return CAPABILITY_DIRECT_SUBMIT /* | CAPABILITY_SUPPORTS_ARBITRARY_AUDIO_DURATION */;
|
||||||
|
}
|
||||||
|
|
||||||
void SoundIoAudioRenderer::sioErrorCallback(SoundIoOutStream* stream, int err)
|
void SoundIoAudioRenderer::sioErrorCallback(SoundIoOutStream* stream, int err)
|
||||||
{
|
{
|
||||||
auto me = reinterpret_cast<SoundIoAudioRenderer*>(stream->userdata);
|
auto me = reinterpret_cast<SoundIoAudioRenderer*>(stream->userdata);
|
||||||
@@ -381,15 +369,19 @@ void SoundIoAudioRenderer::sioWriteCallback(SoundIoOutStream* stream, int frameC
|
|||||||
(me->m_OpusChannelCount * stream->bytes_per_sample);
|
(me->m_OpusChannelCount * stream->bytes_per_sample);
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
|
|
||||||
// Clamp framesLeft to frameCountMax
|
|
||||||
framesLeft = qMin(framesLeft, frameCountMax);
|
|
||||||
|
|
||||||
// Ensure we always write at least a buffer, even if it's silence, to avoid
|
// Ensure we always write at least a buffer, even if it's silence, to avoid
|
||||||
// busy looping when no audio data is available while libsoundio tries to keep
|
// busy looping when no audio data is available while libsoundio tries to keep
|
||||||
// us from starving the output device.
|
// us from starving the output device.
|
||||||
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * k_MinSampleLengthSec));
|
frameCountMin = qMax(frameCountMin, (int)(stream->sample_rate * me->m_AudioPacketDuration));
|
||||||
|
|
||||||
|
// Clamp frameCountMax to at least 2 packets or 20 ms to stop our latency from growing if audio packets lag.
|
||||||
|
// This makes sure that we never increase our latency far beyond what the sink is consuming.
|
||||||
|
frameCountMax = qMin(frameCountMax, (int)(stream->sample_rate * qMax(me->m_AudioPacketDuration * 2, 0.020)));
|
||||||
frameCountMin = qMin(frameCountMin, frameCountMax);
|
frameCountMin = qMin(frameCountMin, frameCountMax);
|
||||||
|
|
||||||
|
// Clamp framesLeft to frameCountMax
|
||||||
|
framesLeft = qMin(framesLeft, frameCountMax);
|
||||||
|
|
||||||
// Track latency on queueing-based backends
|
// Track latency on queueing-based backends
|
||||||
if (me->m_SoundIo->current_backend != SoundIoBackendCoreAudio && me->m_SoundIo->current_backend != SoundIoBackendJack) {
|
if (me->m_SoundIo->current_backend != SoundIoBackendCoreAudio && me->m_SoundIo->current_backend != SoundIoBackendJack) {
|
||||||
soundio_outstream_get_latency(stream, &me->m_Latency);
|
soundio_outstream_get_latency(stream, &me->m_Latency);
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public:
|
|||||||
|
|
||||||
virtual bool submitAudio(int bytesWritten);
|
virtual bool submitAudio(int bytesWritten);
|
||||||
|
|
||||||
|
virtual int getCapabilities();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
@@ -34,9 +36,7 @@ private:
|
|||||||
struct SoundIoOutStream* m_OutputStream;
|
struct SoundIoOutStream* m_OutputStream;
|
||||||
struct SoundIoRingBuffer* m_RingBuffer;
|
struct SoundIoRingBuffer* m_RingBuffer;
|
||||||
struct SoundIoChannelLayout m_EffectiveLayout;
|
struct SoundIoChannelLayout m_EffectiveLayout;
|
||||||
|
double m_AudioPacketDuration;
|
||||||
double m_Latency;
|
double m_Latency;
|
||||||
bool m_Errored;
|
bool m_Errored;
|
||||||
|
|
||||||
static const double k_RawSampleLengthSec;
|
|
||||||
static const double k_MinSampleLengthSec;
|
|
||||||
};
|
};
|
||||||
+95
-13
@@ -41,6 +41,10 @@
|
|||||||
// Determines the maximum motion amount before allowing movement
|
// Determines the maximum motion amount before allowing movement
|
||||||
#define MOUSE_EMULATION_DEADZONE 2
|
#define MOUSE_EMULATION_DEADZONE 2
|
||||||
|
|
||||||
|
// Haptic capabilities (in addition to those from SDL_HapticQuery())
|
||||||
|
#define ML_HAPTIC_GC_RUMBLE (1U << 16)
|
||||||
|
#define ML_HAPTIC_SIMPLE_RUMBLE (1U << 17)
|
||||||
|
|
||||||
const int SdlInputHandler::k_ButtonMap[] = {
|
const int SdlInputHandler::k_ButtonMap[] = {
|
||||||
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
A_FLAG, B_FLAG, X_FLAG, Y_FLAG,
|
||||||
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG,
|
||||||
@@ -51,7 +55,9 @@ const int SdlInputHandler::k_ButtonMap[] = {
|
|||||||
|
|
||||||
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
|
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
|
||||||
: m_MultiController(prefs.multiController),
|
: m_MultiController(prefs.multiController),
|
||||||
|
m_GamepadMouse(prefs.gamepadMouse),
|
||||||
m_MouseMoveTimer(0),
|
m_MouseMoveTimer(0),
|
||||||
|
m_FakeCaptureActive(false),
|
||||||
m_LeftButtonReleaseTimer(0),
|
m_LeftButtonReleaseTimer(0),
|
||||||
m_RightButtonReleaseTimer(0),
|
m_RightButtonReleaseTimer(0),
|
||||||
m_DragTimer(0),
|
m_DragTimer(0),
|
||||||
@@ -71,9 +77,10 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||||||
prefs.mouseAcceleration ? "1" : "0",
|
prefs.mouseAcceleration ? "1" : "0",
|
||||||
SDL_HINT_OVERRIDE);
|
SDL_HINT_OVERRIDE);
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
#if defined(Q_OS_DARWIN) && !SDL_VERSION_ATLEAST(2, 0, 10)
|
||||||
// SDL 2.0.9 on macOS has a broken HIDAPI mapping for the older Xbox One S
|
// SDL 2.0.9 on macOS has a broken HIDAPI mapping for the older Xbox One S
|
||||||
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS.
|
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS until
|
||||||
|
// SDL 2.0.10 where the bug is fixed.
|
||||||
// https://github.com/moonlight-stream/moonlight-qt/issues/133
|
// https://github.com/moonlight-stream/moonlight-qt/issues/133
|
||||||
// https://bugzilla.libsdl.org/show_bug.cgi?id=4395
|
// https://bugzilla.libsdl.org/show_bug.cgi?id=4395
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
|
||||||
@@ -109,12 +116,14 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
||||||
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) {
|
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_InitSubSystem(SDL_INIT_HAPTIC) failed: %s",
|
"SDL_InitSubSystem(SDL_INIT_HAPTIC) failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialize the gamepad mask with currently attached gamepads to avoid
|
// Initialize the gamepad mask with currently attached gamepads to avoid
|
||||||
// causing gamepads to unexpectedly disappear and reappear on the host
|
// causing gamepads to unexpectedly disappear and reappear on the host
|
||||||
@@ -138,9 +147,11 @@ SdlInputHandler::~SdlInputHandler()
|
|||||||
Session::get()->notifyMouseEmulationMode(false);
|
Session::get()->notifyMouseEmulationMode(false);
|
||||||
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
|
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
|
||||||
}
|
}
|
||||||
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
if (m_GamepadState[i].haptic != nullptr) {
|
if (m_GamepadState[i].haptic != nullptr) {
|
||||||
SDL_HapticClose(m_GamepadState[i].haptic);
|
SDL_HapticClose(m_GamepadState[i].haptic);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (m_GamepadState[i].controller != nullptr) {
|
if (m_GamepadState[i].controller != nullptr) {
|
||||||
SDL_GameControllerClose(m_GamepadState[i].controller);
|
SDL_GameControllerClose(m_GamepadState[i].controller);
|
||||||
}
|
}
|
||||||
@@ -151,8 +162,10 @@ SdlInputHandler::~SdlInputHandler()
|
|||||||
SDL_RemoveTimer(m_RightButtonReleaseTimer);
|
SDL_RemoveTimer(m_RightButtonReleaseTimer);
|
||||||
SDL_RemoveTimer(m_DragTimer);
|
SDL_RemoveTimer(m_DragTimer);
|
||||||
|
|
||||||
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
||||||
|
#endif
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
||||||
@@ -211,7 +224,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
"Detected mouse capture toggle combo (SDLK)");
|
"Detected mouse capture toggle combo (SDLK)");
|
||||||
|
|
||||||
// Stop handling future input
|
// Stop handling future input
|
||||||
SDL_SetRelativeMouseMode((SDL_bool)!SDL_GetRelativeMouseMode());
|
setCaptureActive(!isCaptureActive());
|
||||||
|
|
||||||
// Force raise all keys to ensure they aren't stuck,
|
// Force raise all keys to ensure they aren't stuck,
|
||||||
// since we won't get their key up events.
|
// since we won't get their key up events.
|
||||||
@@ -259,7 +272,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
"Detected mouse capture toggle combo (scancode)");
|
"Detected mouse capture toggle combo (scancode)");
|
||||||
|
|
||||||
// Stop handling future input
|
// Stop handling future input
|
||||||
SDL_SetRelativeMouseMode((SDL_bool)!SDL_GetRelativeMouseMode());
|
setCaptureActive(!isCaptureActive());
|
||||||
|
|
||||||
// Force raise all keys to ensure they aren't stuck,
|
// Force raise all keys to ensure they aren't stuck,
|
||||||
// since we won't get their key up events.
|
// since we won't get their key up events.
|
||||||
@@ -292,7 +305,7 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SDL_GetRelativeMouseMode()) {
|
if (!isCaptureActive()) {
|
||||||
// Not capturing
|
// Not capturing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -538,11 +551,11 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
|||||||
// the pressed event was consumed by this code).
|
// the pressed event was consumed by this code).
|
||||||
if (event->button == SDL_BUTTON_LEFT &&
|
if (event->button == SDL_BUTTON_LEFT &&
|
||||||
event->state == SDL_RELEASED &&
|
event->state == SDL_RELEASED &&
|
||||||
!SDL_GetRelativeMouseMode()) {
|
!isCaptureActive()) {
|
||||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
setCaptureActive(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!SDL_GetRelativeMouseMode()) {
|
else if (!isCaptureActive()) {
|
||||||
// Not capturing
|
// Not capturing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -583,7 +596,7 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
|
|||||||
|
|
||||||
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
||||||
{
|
{
|
||||||
if (!SDL_GetRelativeMouseMode()) {
|
if (!isCaptureActive()) {
|
||||||
// Not capturing
|
// Not capturing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -600,7 +613,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
|||||||
|
|
||||||
void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
||||||
{
|
{
|
||||||
if (!SDL_GetRelativeMouseMode()) {
|
if (!isCaptureActive()) {
|
||||||
// Not capturing
|
// Not capturing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -838,7 +851,7 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
|
|||||||
"Mouse emulation deactivated");
|
"Mouse emulation deactivated");
|
||||||
Session::get()->notifyMouseEmulationMode(false);
|
Session::get()->notifyMouseEmulationMode(false);
|
||||||
}
|
}
|
||||||
else {
|
else if (m_GamepadMouse) {
|
||||||
// Send the start button up event to the host, since we won't do it below
|
// Send the start button up event to the host, since we won't do it below
|
||||||
sendGamepadState(state);
|
sendGamepadState(state);
|
||||||
|
|
||||||
@@ -902,6 +915,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
SDL_GameController* controller;
|
SDL_GameController* controller;
|
||||||
const char* mapping;
|
const char* mapping;
|
||||||
char guidStr[33];
|
char guidStr[33];
|
||||||
|
uint32_t hapticCaps;
|
||||||
|
|
||||||
controller = SDL_GameControllerOpen(event->which);
|
controller = SDL_GameControllerOpen(event->which);
|
||||||
if (controller == NULL) {
|
if (controller == NULL) {
|
||||||
@@ -944,10 +958,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
|
|
||||||
state->controller = controller;
|
state->controller = controller;
|
||||||
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
|
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
|
||||||
|
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
|
// Perform a no-op rumble to see if haptics are supported
|
||||||
|
hapticCaps = SDL_GameControllerRumble(controller, 0, 0, 0) == 0 ?
|
||||||
|
ML_HAPTIC_GC_RUMBLE : 0;
|
||||||
|
#else
|
||||||
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
|
state->haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(state->controller));
|
||||||
state->hapticEffectId = -1;
|
state->hapticEffectId = -1;
|
||||||
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_NONE;
|
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_NONE;
|
||||||
if (state->haptic != nullptr) {
|
if (state->haptic != nullptr) {
|
||||||
|
// Query for supported haptic effects
|
||||||
|
hapticCaps = SDL_HapticQuery(state->haptic);
|
||||||
|
hapticCaps |= SDL_HapticRumbleSupported(state->haptic) ?
|
||||||
|
ML_HAPTIC_SIMPLE_RUMBLE : 0;
|
||||||
|
|
||||||
if ((SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
if ((SDL_HapticQuery(state->haptic) & SDL_HAPTIC_LEFTRIGHT) == 0) {
|
||||||
if (SDL_HapticRumbleSupported(state->haptic)) {
|
if (SDL_HapticRumbleSupported(state->haptic)) {
|
||||||
if (SDL_HapticRumbleInit(state->haptic) == 0) {
|
if (SDL_HapticRumbleInit(state->haptic) == 0) {
|
||||||
@@ -962,16 +987,21 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_LEFTRIGHT;
|
state->hapticMethod = GAMEPAD_HAPTIC_METHOD_LEFTRIGHT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
hapticCaps = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
|
SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(state->controller)),
|
||||||
guidStr, sizeof(guidStr));
|
guidStr, sizeof(guidStr));
|
||||||
mapping = SDL_GameControllerMapping(state->controller);
|
mapping = SDL_GameControllerMapping(state->controller);
|
||||||
name = SDL_GameControllerName(state->controller);
|
name = SDL_GameControllerName(state->controller);
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Gamepad %d (player %d) is: %s (%s -> %s)",
|
"Gamepad %d (player %d) is: %s (haptic capabilities: 0x%x) (mapping: %s -> %s)",
|
||||||
i,
|
i,
|
||||||
state->index,
|
state->index,
|
||||||
name != nullptr ? name : "<null>",
|
name != nullptr ? name : "<null>",
|
||||||
|
hapticCaps,
|
||||||
guidStr,
|
guidStr,
|
||||||
mapping != nullptr ? mapping : "<null>");
|
mapping != nullptr ? mapping : "<null>");
|
||||||
if (mapping != nullptr) {
|
if (mapping != nullptr) {
|
||||||
@@ -1001,9 +1031,12 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_GameControllerClose(state->controller);
|
SDL_GameControllerClose(state->controller);
|
||||||
|
|
||||||
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
if (state->haptic != nullptr) {
|
if (state->haptic != nullptr) {
|
||||||
SDL_HapticClose(state->haptic);
|
SDL_HapticClose(state->haptic);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Remove this from the gamepad mask in MC-mode
|
// Remove this from the gamepad mask in MC-mode
|
||||||
if (m_MultiController) {
|
if (m_MultiController) {
|
||||||
@@ -1064,6 +1097,11 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
|
if (m_GamepadState[controllerNumber].controller != nullptr) {
|
||||||
|
SDL_GameControllerRumble(m_GamepadState[controllerNumber].controller, lowFreqMotor, highFreqMotor, 30000);
|
||||||
|
}
|
||||||
|
#else
|
||||||
// Check if the controller supports haptics (and if the controller exists at all)
|
// Check if the controller supports haptics (and if the controller exists at all)
|
||||||
SDL_Haptic* haptic = m_GamepadState[controllerNumber].haptic;
|
SDL_Haptic* haptic = m_GamepadState[controllerNumber].haptic;
|
||||||
if (haptic == nullptr) {
|
if (haptic == nullptr) {
|
||||||
@@ -1107,13 +1145,28 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low
|
|||||||
GAMEPAD_HAPTIC_SIMPLE_LOWFREQ_MOTOR_WEIGHT*lowFreqMotor) / 65535.0),
|
GAMEPAD_HAPTIC_SIMPLE_LOWFREQ_MOTOR_WEIGHT*lowFreqMotor) / 65535.0),
|
||||||
SDL_HAPTIC_INFINITY);
|
SDL_HAPTIC_INFINITY);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
|
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
|
||||||
{
|
{
|
||||||
int fingerIndex = -1;
|
int fingerIndex = -1;
|
||||||
|
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 10)
|
||||||
|
if (SDL_GetTouchDeviceType(event->touchId) != SDL_TOUCH_DEVICE_DIRECT) {
|
||||||
|
// Ignore anything that isn't a touchscreen. We may get callbacks
|
||||||
|
// for trackpads, but we want to handle those in the mouse path.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_DARWIN)
|
||||||
|
// SDL2 sends touch events from trackpads by default on
|
||||||
|
// macOS. This totally screws our actual mouse handling,
|
||||||
|
// so we must explicitly ignore touch events on macOS
|
||||||
|
// until SDL 2.0.10 where we have SDL_GetTouchDeviceType()
|
||||||
|
// to tell them apart.
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Observations on Windows 10: x and y appear to be relative to 0,0 of the window client area.
|
// Observations on Windows 10: x and y appear to be relative to 0,0 of the window client area.
|
||||||
// Although SDL documentation states they are 0.0 - 1.0 float values, they can actually be higher
|
// Although SDL documentation states they are 0.0 - 1.0 float values, they can actually be higher
|
||||||
// or lower than those values as touch events continue for touches started within the client area that
|
// or lower than those values as touch events continue for touches started within the client area that
|
||||||
@@ -1282,6 +1335,35 @@ void SdlInputHandler::raiseAllKeys()
|
|||||||
m_KeysDown.clear();
|
m_KeysDown.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SdlInputHandler::isCaptureActive()
|
||||||
|
{
|
||||||
|
if (SDL_GetRelativeMouseMode()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some platforms don't support SDL_SetRelativeMouseMode
|
||||||
|
return m_FakeCaptureActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SdlInputHandler::setCaptureActive(bool active)
|
||||||
|
{
|
||||||
|
if (active) {
|
||||||
|
// Try to activate SDL's relative mouse mode
|
||||||
|
if (SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
|
||||||
|
// Relative mouse mode didn't work, so we'll use fake capture
|
||||||
|
SDL_ShowCursor(SDL_DISABLE);
|
||||||
|
m_FakeCaptureActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_FakeCaptureActive) {
|
||||||
|
SDL_ShowCursor(SDL_ENABLE);
|
||||||
|
m_FakeCaptureActive = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString SdlInputHandler::getUnmappedGamepads()
|
QString SdlInputHandler::getUnmappedGamepads()
|
||||||
{
|
{
|
||||||
QString ret;
|
QString ret;
|
||||||
|
|||||||
+10
-1
@@ -8,10 +8,13 @@
|
|||||||
struct GamepadState {
|
struct GamepadState {
|
||||||
SDL_GameController* controller;
|
SDL_GameController* controller;
|
||||||
SDL_JoystickID jsId;
|
SDL_JoystickID jsId;
|
||||||
|
short index;
|
||||||
|
|
||||||
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
||||||
SDL_Haptic* haptic;
|
SDL_Haptic* haptic;
|
||||||
int hapticMethod;
|
int hapticMethod;
|
||||||
int hapticEffectId;
|
int hapticEffectId;
|
||||||
short index;
|
#endif
|
||||||
|
|
||||||
SDL_TimerID mouseEmulationTimer;
|
SDL_TimerID mouseEmulationTimer;
|
||||||
uint32_t lastStartDownTime;
|
uint32_t lastStartDownTime;
|
||||||
@@ -64,6 +67,10 @@ public:
|
|||||||
|
|
||||||
void raiseAllKeys();
|
void raiseAllKeys();
|
||||||
|
|
||||||
|
bool isCaptureActive();
|
||||||
|
|
||||||
|
void setCaptureActive(bool active);
|
||||||
|
|
||||||
static
|
static
|
||||||
QString getUnmappedGamepads();
|
QString getUnmappedGamepads();
|
||||||
|
|
||||||
@@ -89,12 +96,14 @@ private:
|
|||||||
Uint32 mouseEmulationTimerCallback(Uint32 interval, void* param);
|
Uint32 mouseEmulationTimerCallback(Uint32 interval, void* param);
|
||||||
|
|
||||||
bool m_MultiController;
|
bool m_MultiController;
|
||||||
|
bool m_GamepadMouse;
|
||||||
SDL_TimerID m_MouseMoveTimer;
|
SDL_TimerID m_MouseMoveTimer;
|
||||||
SDL_atomic_t m_MouseDeltaX;
|
SDL_atomic_t m_MouseDeltaX;
|
||||||
SDL_atomic_t m_MouseDeltaY;
|
SDL_atomic_t m_MouseDeltaY;
|
||||||
int m_GamepadMask;
|
int m_GamepadMask;
|
||||||
GamepadState m_GamepadState[MAX_GAMEPADS];
|
GamepadState m_GamepadState[MAX_GAMEPADS];
|
||||||
QSet<short> m_KeysDown;
|
QSet<short> m_KeysDown;
|
||||||
|
bool m_FakeCaptureActive;
|
||||||
|
|
||||||
SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS];
|
SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS];
|
||||||
float m_CumulativeDelta[MAX_FINGERS];
|
float m_CumulativeDelta[MAX_FINGERS];
|
||||||
|
|||||||
+81
-60
@@ -1,6 +1,7 @@
|
|||||||
#include "session.h"
|
#include "session.h"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
#include "streaming/streamutils.h"
|
#include "streaming/streamutils.h"
|
||||||
|
#include "backend/richpresencemanager.h"
|
||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
@@ -43,22 +44,6 @@ CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
|
|||||||
Session::clConnectionStatusUpdate
|
Session::clConnectionStatusUpdate
|
||||||
};
|
};
|
||||||
|
|
||||||
AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
|
||||||
Session::arInit,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
Session::arCleanup,
|
|
||||||
Session::arDecodeAndPlaySample,
|
|
||||||
#ifndef STEAM_LINK
|
|
||||||
CAPABILITY_DIRECT_SUBMIT
|
|
||||||
#else
|
|
||||||
// We cannot use direct submit for Steam Link because the
|
|
||||||
// SLAudio renderer needs to look at the audio backlog to
|
|
||||||
// cap latency since playback is a blocking operation.
|
|
||||||
CAPABILITY_SLOW_OPUS_DECODER
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
Session* Session::s_ActiveSession;
|
Session* Session::s_ActiveSession;
|
||||||
QSemaphore Session::s_ActiveSessionSemaphore(1);
|
QSemaphore Session::s_ActiveSessionSemaphore(1);
|
||||||
|
|
||||||
@@ -282,21 +267,28 @@ bool Session::isHardwareDecodeAvailable(SDL_Window* window,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Session::getDecoderCapabilities(SDL_Window* window,
|
bool Session::populateDecoderProperties(SDL_Window* window)
|
||||||
StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
int videoFormat, int width, int height, int frameRate)
|
|
||||||
{
|
{
|
||||||
IVideoDecoder* decoder;
|
IVideoDecoder* decoder;
|
||||||
|
|
||||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, false, true, decoder)) {
|
if (!chooseDecoder(m_Preferences->videoDecoderSelection,
|
||||||
|
window,
|
||||||
|
m_StreamConfig.enableHdr ? VIDEO_FORMAT_H265_MAIN10 :
|
||||||
|
(m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264),
|
||||||
|
m_StreamConfig.width,
|
||||||
|
m_StreamConfig.height,
|
||||||
|
m_StreamConfig.fps,
|
||||||
|
true, false, true, decoder)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int caps = decoder->getDecoderCapabilities();
|
m_VideoCallbacks.capabilities = decoder->getDecoderCapabilities();
|
||||||
|
|
||||||
|
m_StreamConfig.colorSpace = decoder->getDecoderColorspace();
|
||||||
|
|
||||||
delete decoder;
|
delete decoder;
|
||||||
|
|
||||||
return caps;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
|
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
|
||||||
@@ -358,21 +350,12 @@ bool Session::initialize()
|
|||||||
m_VideoCallbacks.setup = drSetup;
|
m_VideoCallbacks.setup = drSetup;
|
||||||
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
m_VideoCallbacks.submitDecodeUnit = drSubmitDecodeUnit;
|
||||||
|
|
||||||
// Slice up to 4 times for parallel decode, once slice per core
|
|
||||||
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
|
|
||||||
m_VideoCallbacks.capabilities |= CAPABILITY_SLICES_PER_FRAME(slices);
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Encoder configured for %d slices per frame",
|
|
||||||
slices);
|
|
||||||
|
|
||||||
LiInitializeStreamConfiguration(&m_StreamConfig);
|
LiInitializeStreamConfiguration(&m_StreamConfig);
|
||||||
m_StreamConfig.width = m_Preferences->width;
|
m_StreamConfig.width = m_Preferences->width;
|
||||||
m_StreamConfig.height = m_Preferences->height;
|
m_StreamConfig.height = m_Preferences->height;
|
||||||
m_StreamConfig.fps = m_Preferences->fps;
|
m_StreamConfig.fps = m_Preferences->fps;
|
||||||
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
||||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
||||||
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
|
||||||
m_StreamConfig.packetSize = 1392;
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Video bitrate: %d kbps",
|
"Video bitrate: %d kbps",
|
||||||
@@ -394,6 +377,12 @@ bool Session::initialize()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiInitializeAudioCallbacks(&m_AudioCallbacks);
|
||||||
|
m_AudioCallbacks.init = arInit;
|
||||||
|
m_AudioCallbacks.cleanup = arCleanup;
|
||||||
|
m_AudioCallbacks.decodeAndPlaySample = arDecodeAndPlaySample;
|
||||||
|
m_AudioCallbacks.capabilities = getAudioRendererCapabilities(m_StreamConfig.audioConfiguration);
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Audio configuration: %d",
|
"Audio configuration: %d",
|
||||||
m_StreamConfig.audioConfiguration);
|
m_StreamConfig.audioConfiguration);
|
||||||
@@ -441,22 +430,13 @@ bool Session::initialize()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the capability flags from the chosen decoder/renderer
|
|
||||||
// Requires m_StreamConfig.supportsHevc to be initialized
|
|
||||||
m_VideoCallbacks.capabilities |= getDecoderCapabilities(testWindow,
|
|
||||||
m_Preferences->videoDecoderSelection,
|
|
||||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
|
||||||
m_StreamConfig.width,
|
|
||||||
m_StreamConfig.height,
|
|
||||||
m_StreamConfig.fps);
|
|
||||||
|
|
||||||
switch (m_Preferences->windowMode)
|
switch (m_Preferences->windowMode)
|
||||||
{
|
{
|
||||||
|
default:
|
||||||
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
case StreamingPreferences::WM_FULLSCREEN_DESKTOP:
|
||||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
break;
|
break;
|
||||||
case StreamingPreferences::WM_FULLSCREEN:
|
case StreamingPreferences::WM_FULLSCREEN:
|
||||||
default:
|
|
||||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -465,6 +445,12 @@ bool Session::initialize()
|
|||||||
// signals for them, if appropriate
|
// signals for them, if appropriate
|
||||||
bool ret = validateLaunch(testWindow);
|
bool ret = validateLaunch(testWindow);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
// Populate decoder-dependent properties.
|
||||||
|
// Must be done after validateLaunch() since m_StreamConfig is finalized.
|
||||||
|
ret = populateDecoderProperties(testWindow);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_DestroyWindow(testWindow);
|
SDL_DestroyWindow(testWindow);
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
@@ -511,13 +497,14 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
|||||||
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
bool hevcForced = m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC ||
|
||||||
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
m_Preferences->videoCodecConfig == StreamingPreferences::VCC_FORCE_HEVC_HDR;
|
||||||
|
|
||||||
if (!isHardwareDecodeAvailable(testWindow,
|
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
||||||
m_Preferences->videoDecoderSelection,
|
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Already checked in initialize()
|
||||||
VIDEO_FORMAT_H265,
|
!isHardwareDecodeAvailable(testWindow,
|
||||||
m_StreamConfig.width,
|
m_Preferences->videoDecoderSelection,
|
||||||
m_StreamConfig.height,
|
VIDEO_FORMAT_H265,
|
||||||
m_StreamConfig.fps) &&
|
m_StreamConfig.width,
|
||||||
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO) {
|
m_StreamConfig.height,
|
||||||
|
m_StreamConfig.fps)) {
|
||||||
if (hevcForced) {
|
if (hevcForced) {
|
||||||
emitLaunchWarning("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.");
|
emitLaunchWarning("Using software decoding due to your selection to force HEVC without GPU support. This may cause poor streaming performance.");
|
||||||
}
|
}
|
||||||
@@ -603,6 +590,7 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||||
|
!m_StreamConfig.enableHdr && // HEVC Main10 was already checked for hardware decode support above
|
||||||
!isHardwareDecodeAvailable(testWindow,
|
!isHardwareDecodeAvailable(testWindow,
|
||||||
m_Preferences->videoDecoderSelection,
|
m_Preferences->videoDecoderSelection,
|
||||||
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
m_StreamConfig.supportsHevc ? VIDEO_FORMAT_H265 : VIDEO_FORMAT_H264,
|
||||||
@@ -946,9 +934,22 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
hostInfo.serverInfoGfeVersion = siGfeVersion.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isReachableOverVpn() does network I/O, so we only attempt to check
|
||||||
|
// VPN reachability if we've already contacted the PC successfully
|
||||||
|
if (m_Computer->isReachableOverVpn()) {
|
||||||
|
// It looks like our route to this PC is over a VPN.
|
||||||
|
// Treat it as remote even if the target address is in RFC 1918 address space.
|
||||||
|
m_StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
|
||||||
|
m_StreamConfig.packetSize = 1024;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
||||||
|
m_StreamConfig.packetSize = 1392;
|
||||||
|
}
|
||||||
|
|
||||||
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
||||||
&m_VideoCallbacks,
|
&m_VideoCallbacks,
|
||||||
m_AudioDisabled ? nullptr : &k_AudioCallbacks,
|
m_AudioDisabled ? nullptr : &m_AudioCallbacks,
|
||||||
NULL, 0, NULL, 0);
|
NULL, 0, NULL, 0);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// We already displayed an error dialog in the stage failure
|
// We already displayed an error dialog in the stage failure
|
||||||
@@ -1033,7 +1034,11 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// This prevents the mouse from becoming trapped inside
|
// This prevents the mouse from becoming trapped inside
|
||||||
// Moonlight when it's halted at a debug break.
|
// Moonlight when it's halted at a debug break.
|
||||||
if (m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED) {
|
if (m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED) {
|
||||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
// HACK: This doesn't work on Wayland until we render a frame, so
|
||||||
|
// just don't do it for now.
|
||||||
|
if (strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) {
|
||||||
|
m_InputHandler->setCaptureActive(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1057,6 +1062,9 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// (m_UnexpectedTermination is set back to true).
|
// (m_UnexpectedTermination is set back to true).
|
||||||
m_UnexpectedTermination = false;
|
m_UnexpectedTermination = false;
|
||||||
|
|
||||||
|
// Start rich presence to indicate we're in game
|
||||||
|
RichPresenceManager presence(prefs, m_App.name);
|
||||||
|
|
||||||
// Hijack this thread to be the SDL main thread. We have to do this
|
// Hijack this thread to be the SDL main thread. We have to do this
|
||||||
// because we want to suspend all Qt processing until the stream is over.
|
// because we want to suspend all Qt processing until the stream is over.
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
@@ -1073,6 +1081,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
// ARM core in the Steam Link, so we will wait 10 ms instead.
|
// ARM core in the Steam Link, so we will wait 10 ms instead.
|
||||||
SDL_Delay(10);
|
SDL_Delay(10);
|
||||||
#endif
|
#endif
|
||||||
|
presence.runCallbacks();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@@ -1106,20 +1115,19 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
SDL_GetWindowPosition(m_Window, &x, &y);
|
SDL_GetWindowPosition(m_Window, &x, &y);
|
||||||
SDL_GetWindowSize(m_Window, &width, &height);
|
SDL_GetWindowSize(m_Window, &width, &height);
|
||||||
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height) {
|
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height) {
|
||||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
m_InputHandler->setCaptureActive(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
||||||
// Release mouse cursor when another window is activated (e.g. by using ALT+TAB).
|
// Release mouse cursor when another window is activated (e.g. by using ALT+TAB).
|
||||||
// This lets user to interact with our window's title bar and with the buttons in it.
|
// This lets user to interact with our window's title bar and with the buttons in it.
|
||||||
// Doing this while the window is full-screen breaks the transition out of FS
|
// Doing this while the window is full-screen breaks the transition out of FS
|
||||||
// (desktop and exclusive), so we must check for that before releasing mouse capture.
|
// (desktop and exclusive), so we must check for that before releasing mouse capture.
|
||||||
if (!(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN)) {
|
if (!(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN)) {
|
||||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
m_InputHandler->setCaptureActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raise all keys that are currently pressed. If we don't do this, certain keys
|
// Raise all keys that are currently pressed. If we don't do this, certain keys
|
||||||
@@ -1241,24 +1249,18 @@ void Session::exec(int displayOriginX, int displayOriginY)
|
|||||||
case SDL_JOYDEVICEADDED:
|
case SDL_JOYDEVICEADDED:
|
||||||
m_InputHandler->handleJoystickArrivalEvent(&event.jdevice);
|
m_InputHandler->handleJoystickArrivalEvent(&event.jdevice);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// SDL2 sends touch events from trackpads by default on
|
|
||||||
// macOS. This totally screws our actual mouse handling,
|
|
||||||
// so we must explicitly ignore touch events on macOS.
|
|
||||||
#ifndef Q_OS_DARWIN
|
|
||||||
case SDL_FINGERDOWN:
|
case SDL_FINGERDOWN:
|
||||||
case SDL_FINGERMOTION:
|
case SDL_FINGERMOTION:
|
||||||
case SDL_FINGERUP:
|
case SDL_FINGERUP:
|
||||||
m_InputHandler->handleTouchFingerEvent(&event.tfinger);
|
m_InputHandler->handleTouchFingerEvent(&event.tfinger);
|
||||||
break;
|
break;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchDeferredCleanup:
|
DispatchDeferredCleanup:
|
||||||
// Uncapture the mouse and hide the window immediately,
|
// Uncapture the mouse and hide the window immediately,
|
||||||
// so we can return to the Qt GUI ASAP.
|
// so we can return to the Qt GUI ASAP.
|
||||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
m_InputHandler->setCaptureActive(false);
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
||||||
|
|
||||||
@@ -1280,6 +1282,25 @@ DispatchDeferredCleanup:
|
|||||||
m_VideoDecoder = nullptr;
|
m_VideoDecoder = nullptr;
|
||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
SDL_AtomicUnlock(&m_DecoderLock);
|
||||||
|
|
||||||
|
if (strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
|
||||||
|
// HACK: SDL (as of 2.0.10) has a bug that causes Mutter not to destroy the window
|
||||||
|
// surface when in full-screen unless we render more frames after we request
|
||||||
|
// to exit full-screen. The amount of frames required is variable but 500 ms
|
||||||
|
// of frames seems sufficient in my testing.
|
||||||
|
SDL_SetWindowFullscreen(m_Window, 0);
|
||||||
|
SDL_Renderer* renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_PRESENTVSYNC);
|
||||||
|
if (renderer != nullptr) {
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
SDL_Delay(50);
|
||||||
|
}
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This must be called after the decoder is deleted, because
|
// This must be called after the decoder is deleted, because
|
||||||
// the renderer may want to interact with the window
|
// the renderer may want to interact with the window
|
||||||
SDL_DestroyWindow(m_Window);
|
SDL_DestroyWindow(m_Window);
|
||||||
|
|||||||
@@ -61,17 +61,14 @@ private:
|
|||||||
|
|
||||||
void emitLaunchWarning(QString text);
|
void emitLaunchWarning(QString text);
|
||||||
|
|
||||||
static
|
bool populateDecoderProperties(SDL_Window* window);
|
||||||
int getDecoderCapabilities(SDL_Window* window,
|
|
||||||
StreamingPreferences::VideoDecoderSelection vds,
|
|
||||||
int videoFormat, int width, int height, int frameRate);
|
|
||||||
|
|
||||||
IAudioRenderer* createAudioRenderer();
|
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
|
||||||
|
|
||||||
int detectAudioConfiguration();
|
|
||||||
|
|
||||||
bool testAudio(int audioConfiguration);
|
bool testAudio(int audioConfiguration);
|
||||||
|
|
||||||
|
int getAudioRendererCapabilities(int audioConfiguration);
|
||||||
|
|
||||||
void getWindowDimensions(int& x, int& y,
|
void getWindowDimensions(int& x, int& y,
|
||||||
int& width, int& height);
|
int& width, int& height);
|
||||||
|
|
||||||
@@ -129,6 +126,7 @@ private:
|
|||||||
StreamingPreferences* m_Preferences;
|
StreamingPreferences* m_Preferences;
|
||||||
STREAM_CONFIGURATION m_StreamConfig;
|
STREAM_CONFIGURATION m_StreamConfig;
|
||||||
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
|
DECODER_RENDERER_CALLBACKS m_VideoCallbacks;
|
||||||
|
AUDIO_RENDERER_CALLBACKS m_AudioCallbacks;
|
||||||
NvComputer* m_Computer;
|
NvComputer* m_Computer;
|
||||||
NvApp m_App;
|
NvApp m_App;
|
||||||
SDL_Window* m_Window;
|
SDL_Window* m_Window;
|
||||||
@@ -158,7 +156,6 @@ private:
|
|||||||
|
|
||||||
Overlay::OverlayManager m_OverlayManager;
|
Overlay::OverlayManager m_OverlayManager;
|
||||||
|
|
||||||
static AUDIO_RENDERER_CALLBACKS k_AudioCallbacks;
|
|
||||||
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
static CONNECTION_LISTENER_CALLBACKS k_ConnCallbacks;
|
||||||
static Session* s_ActiveSession;
|
static Session* s_ActiveSession;
|
||||||
static QSemaphore s_ActiveSessionSemaphore;
|
static QSemaphore s_ActiveSessionSemaphore;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public:
|
|||||||
virtual bool initialize(PDECODER_PARAMETERS params) = 0;
|
virtual bool initialize(PDECODER_PARAMETERS params) = 0;
|
||||||
virtual bool isHardwareAccelerated() = 0;
|
virtual bool isHardwareAccelerated() = 0;
|
||||||
virtual int getDecoderCapabilities() = 0;
|
virtual int getDecoderCapabilities() = 0;
|
||||||
|
virtual int getDecoderColorspace() = 0;
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||||
virtual void renderFrameOnMainThread() = 0;
|
virtual void renderFrameOnMainThread() = 0;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include "cuda.h"
|
||||||
|
|
||||||
|
CUDARenderer::CUDARenderer()
|
||||||
|
: m_HwContext(nullptr)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CUDARenderer::~CUDARenderer()
|
||||||
|
{
|
||||||
|
if (m_HwContext != nullptr) {
|
||||||
|
av_buffer_unref(&m_HwContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CUDARenderer::initialize(PDECODER_PARAMETERS)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = av_hwdevice_ctx_create(&m_HwContext, AV_HWDEVICE_TYPE_CUDA, nullptr, nullptr, 0);
|
||||||
|
if (err != 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"av_hwdevice_ctx_create(CUDA) failed: %d",
|
||||||
|
err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CUDARenderer::prepareDecoderContext(AVCodecContext* context)
|
||||||
|
{
|
||||||
|
context->hw_device_ctx = av_buffer_ref(m_HwContext);
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Using CUDA accelerated decoder");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CUDARenderer::renderFrame(AVFrame*)
|
||||||
|
{
|
||||||
|
// We only support indirect rendering
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CUDARenderer::needsTestFrame()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CUDARenderer::isDirectRenderingSupported()
|
||||||
|
{
|
||||||
|
// We only support rendering via SDL read-back
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "renderer.h"
|
||||||
|
|
||||||
|
class CUDARenderer : public IFFmpegRenderer {
|
||||||
|
public:
|
||||||
|
CUDARenderer();
|
||||||
|
virtual ~CUDARenderer() override;
|
||||||
|
virtual bool initialize(PDECODER_PARAMETERS) override;
|
||||||
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
|
virtual bool needsTestFrame() override;
|
||||||
|
virtual bool isDirectRenderingSupported() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AVBufferRef* m_HwContext;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -382,9 +382,6 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
|||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
// TODO: Update for HEVC Main10
|
|
||||||
SDL_assert(m_VideoFormat != VIDEO_FORMAT_H265_MAIN10);
|
|
||||||
|
|
||||||
if (qgetenv("DXVA2_DISABLE_DECODER_BLACKLIST") == "1") {
|
if (qgetenv("DXVA2_DISABLE_DECODER_BLACKLIST") == "1") {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"DXVA2 decoder blacklist is disabled");
|
"DXVA2 decoder blacklist is disabled");
|
||||||
@@ -432,6 +429,15 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
|||||||
"GPU blacklisted for HEVC due to hybrid decode");
|
"GPU blacklisted for HEVC due to hybrid decode");
|
||||||
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
result = (m_VideoFormat & VIDEO_FORMAT_MASK_H265) != 0;
|
||||||
break;
|
break;
|
||||||
|
case 0x1900: // Skylake
|
||||||
|
// Blacklist these for HEVC Main10 to avoid hybrid decode.
|
||||||
|
// Regular HEVC Main is fine though.
|
||||||
|
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"GPU blacklisted for HEVC Main10 due to hybrid decode");
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Intel drivers from before late-2017 had a bug that caused some strange artifacts
|
// Intel drivers from before late-2017 had a bug that caused some strange artifacts
|
||||||
// when decoding HEVC. Avoid HEVC on drivers prior to build 4836 which I confirmed
|
// when decoding HEVC. Avoid HEVC on drivers prior to build 4836 which I confirmed
|
||||||
@@ -545,6 +551,8 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
d3dpp.BackBufferWidth = currentMode.Width;
|
d3dpp.BackBufferWidth = currentMode.Width;
|
||||||
d3dpp.BackBufferHeight = currentMode.Height;
|
d3dpp.BackBufferHeight = currentMode.Height;
|
||||||
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
||||||
|
|
||||||
|
// TODO: Need to select 10-bit color format for HDR
|
||||||
d3dpp.BackBufferFormat = currentMode.Format;
|
d3dpp.BackBufferFormat = currentMode.Format;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -680,7 +688,13 @@ bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown;
|
m_Desc.SampleFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown;
|
||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
||||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
|
||||||
|
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
|
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('P','0','1','0');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||||
|
}
|
||||||
|
|
||||||
if (!initializeDevice(params->window, params->enableVsync)) {
|
if (!initializeDevice(params->window, params->enableVsync)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -762,6 +776,20 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DXVA2Renderer::getDecoderColorspace()
|
||||||
|
{
|
||||||
|
if (isDXVideoProcessorAPIBlacklisted()) {
|
||||||
|
// StretchRect() assumes Rec 601 on Intel GPUs
|
||||||
|
return COLORSPACE_REC_601;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// VideoProcessBlt() *should* properly handle whatever, since
|
||||||
|
// we provide colorspace information. However, AMD GPUs seem to
|
||||||
|
// always assume Rec 709, so we'll use that as our default.
|
||||||
|
return COLORSPACE_REC_709;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DXVA2Renderer::renderFrame(AVFrame *frame)
|
void DXVA2Renderer::renderFrame(AVFrame *frame)
|
||||||
{
|
{
|
||||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public:
|
|||||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||||
|
virtual int getDecoderColorspace() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initializeDecoder();
|
bool initializeDecoder();
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ void Pacer::renderFrame(AVFrame* frame)
|
|||||||
{
|
{
|
||||||
// Count time spent in Pacer's queues
|
// Count time spent in Pacer's queues
|
||||||
Uint32 beforeRender = SDL_GetTicks();
|
Uint32 beforeRender = SDL_GetTicks();
|
||||||
m_VideoStats->totalPacerTime += beforeRender - frame->pts;
|
m_VideoStats->totalPacerTime += beforeRender - frame->pkt_dts;
|
||||||
|
|
||||||
// Render it
|
// Render it
|
||||||
m_VsyncRenderer->renderFrame(frame);
|
m_VsyncRenderer->renderFrame(frame);
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual int getDecoderColorspace() {
|
||||||
|
// Rec 601 is default
|
||||||
|
return COLORSPACE_REC_601;
|
||||||
|
}
|
||||||
|
|
||||||
virtual FramePacingConstraint getFramePacingConstraint() {
|
virtual FramePacingConstraint getFramePacingConstraint() {
|
||||||
// No pacing preference
|
// No pacing preference
|
||||||
return PACING_ANY;
|
return PACING_ANY;
|
||||||
@@ -47,9 +52,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) {
|
virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) {
|
||||||
// Planar YUV 4:2:0
|
if (videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
SDL_assert(videoFormat != VIDEO_FORMAT_H265_MAIN10);
|
// 10-bit YUV 4:2:0
|
||||||
return AV_PIX_FMT_YUV420P;
|
return AV_PIX_FMT_P010;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Planar YUV 4:2:0
|
||||||
|
return AV_PIX_FMT_YUV420P;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IOverlayRenderer
|
// IOverlayRenderer
|
||||||
|
|||||||
@@ -7,16 +7,10 @@
|
|||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
const std::vector<int> SdlRenderer::k_SwFormats({
|
|
||||||
AV_PIX_FMT_YUV420P,
|
|
||||||
AV_PIX_FMT_NV12,
|
|
||||||
AV_PIX_FMT_NV21
|
|
||||||
});
|
|
||||||
|
|
||||||
SdlRenderer::SdlRenderer()
|
SdlRenderer::SdlRenderer()
|
||||||
: m_Renderer(nullptr),
|
: m_Renderer(nullptr),
|
||||||
m_Texture(nullptr),
|
m_Texture(nullptr),
|
||||||
m_SwPixelFormat(AV_PIX_FMT_YUV420P),
|
m_SwPixelFormat(AV_PIX_FMT_NONE),
|
||||||
m_FontData(Path::readDataFile("ModeSeven.ttf"))
|
m_FontData(Path::readDataFile("ModeSeven.ttf"))
|
||||||
{
|
{
|
||||||
SDL_assert(TTF_WasInit() == 0);
|
SDL_assert(TTF_WasInit() == 0);
|
||||||
@@ -74,13 +68,6 @@ bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SdlRenderer::getDecoderCapabilities()
|
|
||||||
{
|
|
||||||
// The FFmpeg CPU decoder can handle reference frame invalidation,
|
|
||||||
// but only for H.264.
|
|
||||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_AVC;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SdlRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
void SdlRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
||||||
{
|
{
|
||||||
// Construct the required font to render the overlay
|
// Construct the required font to render the overlay
|
||||||
@@ -140,10 +127,25 @@ bool SdlRenderer::isRenderThreadSupported()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SdlRenderer::getDecoderCapabilities()
|
||||||
|
{
|
||||||
|
// Slice up to 4 times for parallel decode, once slice per core
|
||||||
|
int slices = qMin(MAX_SLICES, SDL_GetCPUCount());
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Encoder configured for %d slices per frame",
|
||||||
|
slices);
|
||||||
|
return CAPABILITY_SLICES_PER_FRAME(slices);
|
||||||
|
}
|
||||||
|
|
||||||
bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
|
bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||||
|
|
||||||
|
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
|
// SDL doesn't support rendering YUV 10-bit textures yet
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
if ((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||||
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
|
// In full-screen exclusive mode, we enable V-sync if requested. For other modes, Windows and Mac
|
||||||
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
||||||
@@ -239,6 +241,18 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
// accelerated decoder, we'll need to read the frame
|
// accelerated decoder, we'll need to read the frame
|
||||||
// back to render it.
|
// back to render it.
|
||||||
|
|
||||||
|
// Find the native read-back format
|
||||||
|
if (m_SwPixelFormat == AV_PIX_FMT_NONE) {
|
||||||
|
auto hwFrameCtx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
|
||||||
|
|
||||||
|
m_SwPixelFormat = hwFrameCtx->sw_format;
|
||||||
|
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Selected read-back format: %d",
|
||||||
|
m_SwPixelFormat);
|
||||||
|
}
|
||||||
|
|
||||||
swFrame = av_frame_alloc();
|
swFrame = av_frame_alloc();
|
||||||
if (swFrame == nullptr) {
|
if (swFrame == nullptr) {
|
||||||
return;
|
return;
|
||||||
@@ -249,22 +263,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
swFrame->format = m_SwPixelFormat;
|
swFrame->format = m_SwPixelFormat;
|
||||||
|
|
||||||
err = av_hwframe_transfer_data(swFrame, frame, 0);
|
err = av_hwframe_transfer_data(swFrame, frame, 0);
|
||||||
if (err != 0) {
|
|
||||||
// Try to find a supported format
|
|
||||||
for (uint32_t i = 0; i < k_SwFormats.size(); i++) {
|
|
||||||
swFrame->format = k_SwFormats.at(i);
|
|
||||||
err = av_hwframe_transfer_data(swFrame, frame, 0);
|
|
||||||
if (err == 0) {
|
|
||||||
// The format was supported. Use that automatically in the future.
|
|
||||||
m_SwPixelFormat = swFrame->format;
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Selected read-back format: %d",
|
|
||||||
m_SwPixelFormat);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"av_hwframe_transfer_data() failed: %d",
|
"av_hwframe_transfer_data() failed: %d",
|
||||||
@@ -272,6 +270,11 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
goto Exit;
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// av_hwframe_transfer_data() can nuke frame metadata,
|
||||||
|
// so anything other than width, height, and format must
|
||||||
|
// be set *after* calling av_hwframe_transfer_data().
|
||||||
|
swFrame->colorspace = frame->colorspace;
|
||||||
|
|
||||||
frame = swFrame;
|
frame = swFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +297,18 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
goto Exit;
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (frame->colorspace)
|
||||||
|
{
|
||||||
|
case AVCOL_SPC_BT709:
|
||||||
|
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT709);
|
||||||
|
break;
|
||||||
|
case AVCOL_SPC_BT470BG:
|
||||||
|
case AVCOL_SPC_SMPTE170M:
|
||||||
|
default:
|
||||||
|
SDL_SetYUVConversionMode(SDL_YUV_CONVERSION_BT601);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
m_Texture = SDL_CreateTexture(m_Renderer,
|
m_Texture = SDL_CreateTexture(m_Renderer,
|
||||||
sdlFormat,
|
sdlFormat,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public:
|
|||||||
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual int getDecoderCapabilities() override;
|
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||||
virtual bool isRenderThreadSupported() override;
|
virtual bool isRenderThreadSupported() override;
|
||||||
|
virtual int getDecoderCapabilities() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void renderOverlay(Overlay::OverlayType type);
|
void renderOverlay(Overlay::OverlayType type);
|
||||||
@@ -26,7 +26,5 @@ private:
|
|||||||
SDL_Surface* m_OverlaySurfaces[Overlay::OverlayMax];
|
SDL_Surface* m_OverlaySurfaces[Overlay::OverlayMax];
|
||||||
SDL_Texture* m_OverlayTextures[Overlay::OverlayMax];
|
SDL_Texture* m_OverlayTextures[Overlay::OverlayMax];
|
||||||
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
|
SDL_Rect m_OverlayRects[Overlay::OverlayMax];
|
||||||
|
|
||||||
static const std::vector<int> k_SwFormats;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5,8 +5,12 @@
|
|||||||
|
|
||||||
#include <SDL_syswm.h>
|
#include <SDL_syswm.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
VAAPIRenderer::VAAPIRenderer()
|
VAAPIRenderer::VAAPIRenderer()
|
||||||
: m_HwContext(nullptr)
|
: m_HwContext(nullptr),
|
||||||
|
m_DrmFd(-1)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,6 +30,10 @@ VAAPIRenderer::~VAAPIRenderer()
|
|||||||
vaTerminate(display);
|
vaTerminate(display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_DrmFd != -1) {
|
||||||
|
close(m_DrmFd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -86,6 +94,39 @@ VAAPIRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Moonlight not compiled with VAAPI Wayland support!");
|
"Moonlight not compiled with VAAPI Wayland support!");
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
// TODO: Upstream a better solution for SDL_GetWindowWMInfo on KMSDRM
|
||||||
|
else if (strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") == 0) {
|
||||||
|
#ifdef HAVE_LIBVA_DRM
|
||||||
|
const char* device = SDL_getenv("DRM_DEV");
|
||||||
|
|
||||||
|
if (device == nullptr) {
|
||||||
|
device = "/dev/dri/card0";
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Opening DRM device: %s",
|
||||||
|
device);
|
||||||
|
|
||||||
|
m_DrmFd = open(device, O_RDWR | O_CLOEXEC);
|
||||||
|
if (m_DrmFd < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to open DRM device: %d",
|
||||||
|
errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vaDeviceContext->display = vaGetDisplayDRM(m_DrmFd);
|
||||||
|
if (!vaDeviceContext->display) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unable to open DRM display for VAAPI");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Moonlight not compiled with VAAPI DRM support!");
|
||||||
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -157,7 +198,7 @@ VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
|
|||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Using VAAPI accelerated renderer on %s",
|
"Using VAAPI accelerated renderer on %s",
|
||||||
m_WindowSystem == SDL_SYSWM_X11 ? "X11" : "Wayland");
|
SDL_GetCurrentVideoDriver());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -178,6 +219,13 @@ VAAPIRenderer::isDirectRenderingSupported()
|
|||||||
return m_WindowSystem == SDL_SYSWM_X11;
|
return m_WindowSystem == SDL_SYSWM_X11;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int VAAPIRenderer::getDecoderColorspace()
|
||||||
|
{
|
||||||
|
// Gallium drivers don't support Rec 709 yet - https://gitlab.freedesktop.org/mesa/mesa/issues/1915
|
||||||
|
// Intel-vaapi-driver defaults to Rec 601 - https://github.com/intel/intel-vaapi-driver/blob/021bcb79d1bd873bbd9fbca55f40320344bab866/src/i965_output_dri.c#L186
|
||||||
|
return COLORSPACE_REC_601;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
VAAPIRenderer::renderFrame(AVFrame* frame)
|
VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
@@ -197,6 +245,27 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
|||||||
|
|
||||||
if (m_WindowSystem == SDL_SYSWM_X11) {
|
if (m_WindowSystem == SDL_SYSWM_X11) {
|
||||||
#ifdef HAVE_LIBVA_X11
|
#ifdef HAVE_LIBVA_X11
|
||||||
|
unsigned int flags = 0;
|
||||||
|
|
||||||
|
// NB: Not all VAAPI drivers respect these flags. Many drivers
|
||||||
|
// just ignore them and do the color conversion as Rec 601.
|
||||||
|
switch (frame->colorspace) {
|
||||||
|
case AVCOL_SPC_BT709:
|
||||||
|
flags |= VA_SRC_BT709;
|
||||||
|
break;
|
||||||
|
case AVCOL_SPC_BT470BG:
|
||||||
|
case AVCOL_SPC_SMPTE170M:
|
||||||
|
flags |= VA_SRC_BT601;
|
||||||
|
break;
|
||||||
|
case AVCOL_SPC_SMPTE240M:
|
||||||
|
flags |= VA_SRC_SMPTE_240;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unknown colorspace
|
||||||
|
SDL_assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
vaPutSurface(vaDeviceContext->display,
|
vaPutSurface(vaDeviceContext->display,
|
||||||
surface,
|
surface,
|
||||||
m_XWindow,
|
m_XWindow,
|
||||||
@@ -204,7 +273,7 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
|||||||
m_VideoWidth, m_VideoHeight,
|
m_VideoWidth, m_VideoHeight,
|
||||||
dst.x, dst.y,
|
dst.x, dst.y,
|
||||||
dst.w, dst.h,
|
dst.w, dst.h,
|
||||||
NULL, 0, 0);
|
NULL, 0, flags);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
else if (m_WindowSystem == SDL_SYSWM_WAYLAND) {
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ extern "C" {
|
|||||||
#ifdef HAVE_LIBVA_WAYLAND
|
#ifdef HAVE_LIBVA_WAYLAND
|
||||||
#include <va/va_wayland.h>
|
#include <va/va_wayland.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_LIBVA_DRM
|
||||||
|
#include <va/va_drm.h>
|
||||||
|
#endif
|
||||||
#include <libavutil/hwcontext_vaapi.h>
|
#include <libavutil/hwcontext_vaapi.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +38,12 @@ public:
|
|||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual bool needsTestFrame() override;
|
virtual bool needsTestFrame() override;
|
||||||
virtual bool isDirectRenderingSupported() override;
|
virtual bool isDirectRenderingSupported() override;
|
||||||
|
virtual int getDecoderColorspace() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_WindowSystem;
|
int m_WindowSystem;
|
||||||
AVBufferRef* m_HwContext;
|
AVBufferRef* m_HwContext;
|
||||||
|
int m_DrmFd;
|
||||||
|
|
||||||
#ifdef HAVE_LIBVA_X11
|
#ifdef HAVE_LIBVA_X11
|
||||||
Window m_XWindow;
|
Window m_XWindow;
|
||||||
|
|||||||
@@ -12,11 +12,16 @@
|
|||||||
#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(m_Device, id, (void**)func); \
|
#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(m_Device, id, (void**)func); \
|
||||||
BAIL_ON_FAIL(status, id)
|
BAIL_ON_FAIL(status, id)
|
||||||
|
|
||||||
const VdpRGBAFormat VDPAURenderer::k_OutputFormats[] = {
|
const VdpRGBAFormat VDPAURenderer::k_OutputFormats8Bit[] = {
|
||||||
VDP_RGBA_FORMAT_B8G8R8A8,
|
VDP_RGBA_FORMAT_B8G8R8A8,
|
||||||
VDP_RGBA_FORMAT_R8G8B8A8
|
VDP_RGBA_FORMAT_R8G8B8A8
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const VdpRGBAFormat VDPAURenderer::k_OutputFormats10Bit[] = {
|
||||||
|
VDP_RGBA_FORMAT_B10G10R10A2,
|
||||||
|
VDP_RGBA_FORMAT_R10G10B10A2
|
||||||
|
};
|
||||||
|
|
||||||
VDPAURenderer::VDPAURenderer()
|
VDPAURenderer::VDPAURenderer()
|
||||||
: m_HwContext(nullptr),
|
: m_HwContext(nullptr),
|
||||||
m_PresentationQueueTarget(0),
|
m_PresentationQueueTarget(0),
|
||||||
@@ -143,7 +148,11 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) {
|
for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) {
|
||||||
VdpBool supported;
|
VdpBool supported;
|
||||||
uint32_t maxWidth, maxHeight;
|
uint32_t maxWidth, maxHeight;
|
||||||
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, k_OutputFormats[i],
|
VdpRGBAFormat candidateFormat =
|
||||||
|
params->videoFormat == VIDEO_FORMAT_H265_MAIN10 ?
|
||||||
|
k_OutputFormats10Bit[i] : k_OutputFormats8Bit[i];
|
||||||
|
|
||||||
|
status = m_VdpOutputSurfaceQueryCapabilities(m_Device, candidateFormat,
|
||||||
&supported, &maxWidth, &maxHeight);
|
&supported, &maxWidth, &maxHeight);
|
||||||
if (status != VDP_STATUS_OK) {
|
if (status != VDP_STATUS_OK) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -154,7 +163,7 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
|
|
||||||
if (supported) {
|
if (supported) {
|
||||||
if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) {
|
if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) {
|
||||||
m_OutputSurfaceFormat = k_OutputFormats[i];
|
m_OutputSurfaceFormat = candidateFormat;
|
||||||
foundFormat = true;
|
foundFormat = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -241,6 +250,15 @@ bool VDPAURenderer::needsTestFrame()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int VDPAURenderer::getDecoderColorspace()
|
||||||
|
{
|
||||||
|
// VDPAU defaults to Rec 601.
|
||||||
|
// https://http.download.nvidia.com/XFree86/vdpau/doxygen/html/group___vdp_video_mixer.html#ga65580813e9045d94b739ed2bb8b62b46
|
||||||
|
//
|
||||||
|
// AMD and Nvidia GPUs both correctly process Rec 601, so let's not try our luck using a non-default colorspace.
|
||||||
|
return COLORSPACE_REC_601;
|
||||||
|
}
|
||||||
|
|
||||||
void VDPAURenderer::renderFrame(AVFrame* frame)
|
void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
VdpStatus status;
|
VdpStatus status;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public:
|
|||||||
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual bool needsTestFrame() override;
|
virtual bool needsTestFrame() override;
|
||||||
|
virtual int getDecoderColorspace() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_VideoWidth, m_VideoHeight;
|
uint32_t m_VideoWidth, m_VideoHeight;
|
||||||
@@ -33,7 +34,8 @@ private:
|
|||||||
int m_NextSurfaceIndex;
|
int m_NextSurfaceIndex;
|
||||||
|
|
||||||
#define OUTPUT_SURFACE_FORMAT_COUNT 2
|
#define OUTPUT_SURFACE_FORMAT_COUNT 2
|
||||||
static const VdpRGBAFormat k_OutputFormats[OUTPUT_SURFACE_FORMAT_COUNT];
|
static const VdpRGBAFormat k_OutputFormats8Bit[OUTPUT_SURFACE_FORMAT_COUNT];
|
||||||
|
static const VdpRGBAFormat k_OutputFormats10Bit[OUTPUT_SURFACE_FORMAT_COUNT];
|
||||||
|
|
||||||
VdpGetErrorString* m_VdpGetErrorString;
|
VdpGetErrorString* m_VdpGetErrorString;
|
||||||
VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy;
|
VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#import <VideoToolbox/VideoToolbox.h>
|
#import <VideoToolbox/VideoToolbox.h>
|
||||||
#import <AVFoundation/AVFoundation.h>
|
#import <AVFoundation/AVFoundation.h>
|
||||||
#import <dispatch/dispatch.h>
|
#import <dispatch/dispatch.h>
|
||||||
|
#import <Metal/Metal.h>
|
||||||
|
|
||||||
class VTRenderer : public IFFmpegRenderer
|
class VTRenderer : public IFFmpegRenderer
|
||||||
{
|
{
|
||||||
@@ -23,7 +24,9 @@ public:
|
|||||||
m_DisplayLayer(nullptr),
|
m_DisplayLayer(nullptr),
|
||||||
m_FormatDesc(nullptr),
|
m_FormatDesc(nullptr),
|
||||||
m_StreamView(nullptr),
|
m_StreamView(nullptr),
|
||||||
m_DisplayLink(nullptr)
|
m_DisplayLink(nullptr),
|
||||||
|
m_VsyncMutex(nullptr),
|
||||||
|
m_VsyncPassed(nullptr)
|
||||||
{
|
{
|
||||||
SDL_zero(m_OverlayTextFields);
|
SDL_zero(m_OverlayTextFields);
|
||||||
}
|
}
|
||||||
@@ -118,6 +121,11 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The CVDisplayLink callback uses these, so we must initialize them before
|
||||||
|
// starting the callbacks.
|
||||||
|
m_VsyncMutex = SDL_CreateMutex();
|
||||||
|
m_VsyncPassed = SDL_CreateCond();
|
||||||
|
|
||||||
status = CVDisplayLinkStart(m_DisplayLink);
|
status = CVDisplayLinkStart(m_DisplayLink);
|
||||||
if (status != kCVReturnSuccess) {
|
if (status != kCVReturnSuccess) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -126,9 +134,6 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_VsyncMutex = SDL_CreateMutex();
|
|
||||||
m_VsyncPassed = SDL_CreateCond();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +235,52 @@ public:
|
|||||||
"No HW accelerated HEVC decode via VT");
|
"No HW accelerated HEVC decode via VT");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HEVC Main10 requires more extensive checks because there's no
|
||||||
|
// simple API to check for Main10 hardware decoding, and if we don't
|
||||||
|
// have it, we'll silently get software decoding with horrible performance.
|
||||||
|
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
|
||||||
|
if (__builtin_available(macOS 10.14, *)) {
|
||||||
|
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||||
|
if (device == nullptr) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Unable to get default Metal device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude all GPUs earlier than macOSGPUFamily2
|
||||||
|
// https://developer.apple.com/documentation/metal/mtlfeatureset/mtlfeatureset_macos_gpufamily2_v1
|
||||||
|
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) {
|
||||||
|
if ([device.name containsString:@"Intel"]) {
|
||||||
|
// 500-series Intel GPUs are Skylake and don't support Main10 hardware decoding
|
||||||
|
if ([device.name containsString:@" 5"]) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No HEVC Main10 support on Skylake iGPU");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ([device.name containsString:@"AMD"]) {
|
||||||
|
// FirePro D, M200, and M300 series GPUs don't support Main10 hardware decoding
|
||||||
|
if ([device.name containsString:@"FirePro D"] ||
|
||||||
|
[device.name containsString:@" M2"] ||
|
||||||
|
[device.name containsString:@" M3"]) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No HEVC Main10 support on AMD GPUs until Polaris");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// Fail closed for HEVC Main10 if we're not on 10.14+
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No HEVC Main10 support on < macOS 10.14");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <h264_stream.h>
|
#include <h264_stream.h>
|
||||||
|
|
||||||
#include "ffmpeg-renderers/sdlvid.h"
|
#include "ffmpeg-renderers/sdlvid.h"
|
||||||
|
#include "ffmpeg-renderers/cuda.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#include "ffmpeg-renderers/dxva2.h"
|
#include "ffmpeg-renderers/dxva2.h"
|
||||||
@@ -49,6 +50,11 @@ int FFmpegVideoDecoder::getDecoderCapabilities()
|
|||||||
return m_BackendRenderer->getDecoderCapabilities();
|
return m_BackendRenderer->getDecoderCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int FFmpegVideoDecoder::getDecoderColorspace()
|
||||||
|
{
|
||||||
|
return m_FrontendRenderer->getDecoderColorspace();
|
||||||
|
}
|
||||||
|
|
||||||
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
enum AVPixelFormat FFmpegVideoDecoder::ffGetFormat(AVCodecContext* context,
|
||||||
const enum AVPixelFormat* pixFmts)
|
const enum AVPixelFormat* pixFmts)
|
||||||
{
|
{
|
||||||
@@ -212,6 +218,15 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
|
|||||||
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
|
m_VideoDecoderCtx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT;
|
||||||
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
m_VideoDecoderCtx->flags2 |= AV_CODEC_FLAG2_SHOW_ALL;
|
||||||
|
|
||||||
|
// Report decoding errors to allow us to request a key frame
|
||||||
|
//
|
||||||
|
// With HEVC streams, FFmpeg can drop a frame (hwaccel->start_frame() fails)
|
||||||
|
// without telling us. Since we have an infinite GOP length, this causes artifacts
|
||||||
|
// on screen that persist for a long time. It's easy to cause this condition
|
||||||
|
// by using NVDEC and delaying 100 ms randomly in the render path so the decoder
|
||||||
|
// runs out of output buffers.
|
||||||
|
m_VideoDecoderCtx->err_recognition = AV_EF_EXPLODE;
|
||||||
|
|
||||||
// Enable slice multi-threading for software decoding
|
// Enable slice multi-threading for software decoding
|
||||||
if (!isHardwareAccelerated()) {
|
if (!isHardwareAccelerated()) {
|
||||||
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
m_VideoDecoderCtx->thread_type = FF_THREAD_SLICE;
|
||||||
@@ -254,13 +269,24 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM
|
|||||||
// now to see if things will actually work when the video stream
|
// now to see if things will actually work when the video stream
|
||||||
// comes in.
|
// comes in.
|
||||||
if (testFrame) {
|
if (testFrame) {
|
||||||
if (params->videoFormat & VIDEO_FORMAT_MASK_H264) {
|
switch (params->videoFormat) {
|
||||||
|
case VIDEO_FORMAT_H264:
|
||||||
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
m_Pkt.data = (uint8_t*)k_H264TestFrame;
|
||||||
m_Pkt.size = sizeof(k_H264TestFrame);
|
m_Pkt.size = sizeof(k_H264TestFrame);
|
||||||
}
|
break;
|
||||||
else {
|
case VIDEO_FORMAT_H265:
|
||||||
m_Pkt.data = (uint8_t*)k_HEVCTestFrame;
|
m_Pkt.data = (uint8_t*)k_HEVCMainTestFrame;
|
||||||
m_Pkt.size = sizeof(k_HEVCTestFrame);
|
m_Pkt.size = sizeof(k_HEVCMainTestFrame);
|
||||||
|
break;
|
||||||
|
case VIDEO_FORMAT_H265_MAIN10:
|
||||||
|
m_Pkt.data = (uint8_t*)k_HEVCMain10TestFrame;
|
||||||
|
m_Pkt.size = sizeof(k_HEVCMain10TestFrame);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"No test frame for format: %x",
|
||||||
|
params->videoFormat);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
err = avcodec_send_packet(m_VideoDecoderCtx, &m_Pkt);
|
||||||
@@ -369,31 +395,49 @@ void FFmpegVideoDecoder::logVideoStats(VIDEO_STATS& stats, const char* title)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg)
|
IFFmpegRenderer* FFmpegVideoDecoder::createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg, int pass)
|
||||||
{
|
{
|
||||||
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
if (!(hwDecodeCfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for acceleration types we support
|
// First pass using our top-tier hwaccel implementations
|
||||||
switch (hwDecodeCfg->device_type) {
|
if (pass == 0) {
|
||||||
|
switch (hwDecodeCfg->device_type) {
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
case AV_HWDEVICE_TYPE_DXVA2:
|
case AV_HWDEVICE_TYPE_DXVA2:
|
||||||
return new DXVA2Renderer();
|
return new DXVA2Renderer();
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_DARWIN
|
#ifdef Q_OS_DARWIN
|
||||||
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
case AV_HWDEVICE_TYPE_VIDEOTOOLBOX:
|
||||||
return VTRendererFactory::createRenderer();
|
return VTRendererFactory::createRenderer();
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_LIBVA
|
#ifdef HAVE_LIBVA
|
||||||
case AV_HWDEVICE_TYPE_VAAPI:
|
case AV_HWDEVICE_TYPE_VAAPI:
|
||||||
return new VAAPIRenderer();
|
return new VAAPIRenderer();
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_LIBVDPAU
|
#ifdef HAVE_LIBVDPAU
|
||||||
case AV_HWDEVICE_TYPE_VDPAU:
|
case AV_HWDEVICE_TYPE_VDPAU:
|
||||||
return new VDPAURenderer();
|
return new VDPAURenderer();
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Second pass for our second-tier hwaccel implementations
|
||||||
|
else if (pass == 1) {
|
||||||
|
switch (hwDecodeCfg->device_type) {
|
||||||
|
case AV_HWDEVICE_TYPE_CUDA:
|
||||||
|
// CUDA should only be used if all other options fail, since it requires
|
||||||
|
// read-back of frames. This should only be used for the NVIDIA+Wayland case
|
||||||
|
// with VDPAU covering the NVIDIA+X11 scenario.
|
||||||
|
return new CUDARenderer();
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_assert(false);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,7 +513,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
|||||||
|
|
||||||
// Look for a hardware decoder first unless software-only
|
// Look for a hardware decoder first unless software-only
|
||||||
if (params->vds != StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
if (params->vds != StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||||
// Look for the first matching hwaccel hardware decoder
|
// Look for the first matching hwaccel hardware decoder (pass 0)
|
||||||
for (int i = 0;; i++) {
|
for (int i = 0;; i++) {
|
||||||
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@@ -479,7 +523,7 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
|||||||
|
|
||||||
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
||||||
if (tryInitializeRenderer(decoder, params, config,
|
if (tryInitializeRenderer(decoder, params, config,
|
||||||
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config); })) {
|
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 0); })) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,6 +559,22 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Look for the first matching hwaccel hardware decoder (pass 1)
|
||||||
|
// This picks up "second-tier" hwaccels like CUDA.
|
||||||
|
for (int i = 0;; i++) {
|
||||||
|
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
|
||||||
|
if (!config) {
|
||||||
|
// No remaing hwaccel options
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
||||||
|
if (tryInitializeRenderer(decoder, params, config,
|
||||||
|
[config]() -> IFFmpegRenderer* { return createHwAccelRenderer(config, 1); })) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to software if no matching hardware decoder was found
|
// Fallback to software if no matching hardware decoder was found
|
||||||
@@ -676,8 +736,11 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
// Restore default log level after a successful decode
|
// Restore default log level after a successful decode
|
||||||
av_log_set_level(AV_LOG_INFO);
|
av_log_set_level(AV_LOG_INFO);
|
||||||
|
|
||||||
|
// Store the presentation time
|
||||||
|
frame->pts = du->presentationTimeMs;
|
||||||
|
|
||||||
// Capture a frame timestamp to measuring pacing delay
|
// Capture a frame timestamp to measuring pacing delay
|
||||||
frame->pts = SDL_GetTicks();
|
frame->pkt_dts = SDL_GetTicks();
|
||||||
|
|
||||||
// Count time in avcodec_send_packet() and avcodec_receive_frame()
|
// Count time in avcodec_send_packet() and avcodec_receive_frame()
|
||||||
// as time spent decoding
|
// as time spent decoding
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public:
|
|||||||
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
virtual bool isHardwareAccelerated() override;
|
virtual bool isHardwareAccelerated() override;
|
||||||
virtual int getDecoderCapabilities() override;
|
virtual int getDecoderCapabilities() override;
|
||||||
|
virtual int getDecoderColorspace() override;
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||||
virtual void renderFrameOnMainThread() override;
|
virtual void renderFrameOnMainThread() override;
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ private:
|
|||||||
const AVCodecHWConfig* hwConfig,
|
const AVCodecHWConfig* hwConfig,
|
||||||
std::function<IFFmpegRenderer*()> createRendererFunc);
|
std::function<IFFmpegRenderer*()> createRendererFunc);
|
||||||
|
|
||||||
static IFFmpegRenderer* createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg);
|
static IFFmpegRenderer* createHwAccelRenderer(const AVCodecHWConfig* hwDecodeCfg, int pass);
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
@@ -66,5 +67,6 @@ private:
|
|||||||
bool m_TestOnly;
|
bool m_TestOnly;
|
||||||
|
|
||||||
static const uint8_t k_H264TestFrame[];
|
static const uint8_t k_H264TestFrame[];
|
||||||
static const uint8_t k_HEVCTestFrame[];
|
static const uint8_t k_HEVCMainTestFrame[];
|
||||||
|
static const uint8_t k_HEVCMain10TestFrame[];
|
||||||
};
|
};
|
||||||
@@ -10,10 +10,21 @@ const uint8_t FFmpegVideoDecoder::k_H264TestFrame[] = {
|
|||||||
0x00, 0x00, 0x01, 0x65, 0x00, 0x14, 0xa2, 0xe0, 0x08, 0x5f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xb9, 0x7d, 0xb4, 0x70, 0x4d, 0x15, 0xc5, 0x0a, 0x4e, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x26, 0xa8, 0xb0, 0x00, 0x13, 0xcd, 0xcc, 0x00, 0x07, 0x48, 0x88, 0x00, 0x06, 0x29, 0x69, 0x00, 0x01, 0x16, 0xac, 0x80, 0x00, 0x9e, 0x4e, 0x80, 0x00, 0x3f, 0x84, 0x20, 0x00, 0x6f, 0x41, 0xa0, 0x00, 0x20, 0x00, 0x02, 0x16, 0xb8, 0x00, 0x08, 0x08
|
0x00, 0x00, 0x01, 0x65, 0x00, 0x14, 0xa2, 0xe0, 0x08, 0x5f, 0xe3, 0x85, 0x1c, 0xd9, 0xff, 0xe1, 0x1b, 0x01, 0xfa, 0xc0, 0x3e, 0x11, 0x7e, 0xfe, 0x45, 0x5c, 0x43, 0x69, 0x31, 0x4b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x24, 0xb9, 0x7d, 0xb4, 0x70, 0x4d, 0x15, 0xc5, 0x0a, 0x4e, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x26, 0xa8, 0xb0, 0x00, 0x13, 0xcd, 0xcc, 0x00, 0x07, 0x48, 0x88, 0x00, 0x06, 0x29, 0x69, 0x00, 0x01, 0x16, 0xac, 0x80, 0x00, 0x9e, 0x4e, 0x80, 0x00, 0x3f, 0x84, 0x20, 0x00, 0x6f, 0x41, 0xa0, 0x00, 0x20, 0x00, 0x02, 0x16, 0xb8, 0x00, 0x08, 0x08
|
||||||
};
|
};
|
||||||
|
|
||||||
// 720p 60 FPS HEVC with 1 reference frame
|
// 720p 60 FPS HEVC Main with 1 reference frame
|
||||||
const uint8_t FFmpegVideoDecoder::k_HEVCTestFrame[] = {
|
const uint8_t FFmpegVideoDecoder::k_HEVCMainTestFrame[] = {
|
||||||
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
||||||
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x13, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3, 0x01, 0x6a, 0x0c, 0x0c, 0x0d, 0x48, 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x85, 0xf1, 0xa2, 0xd0,
|
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x13, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3, 0x01, 0x6a, 0x0c, 0x0c, 0x0d, 0x48, 0x20, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x07, 0x85, 0xf1, 0xa2, 0xd0,
|
||||||
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
|
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
|
||||||
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xe6, 0xf6, 0x5e, 0xff, 0xd9, 0x97, 0xeb, 0x44, 0x7d, 0x1f, 0xa4, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x52, 0x7c, 0x4e, 0x5b, 0xfe, 0xbe, 0xc2, 0x87, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x2b, 0x60,
|
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xe6, 0xf6, 0x5e, 0xff, 0xd9, 0x97, 0xeb, 0x44, 0x7d, 0x1f, 0xa4, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x52, 0x7c, 0x4e, 0x5b, 0xfe, 0xbe, 0xc2, 0x87, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x2b, 0x60,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 720p 60 FPS HEVC Main10 with 1 reference frame
|
||||||
|
const uint8_t FFmpegVideoDecoder::k_HEVCMain10TestFrame[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xac, 0x09,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2e, 0x1f, 0x12, 0xd9, 0x6b, 0x4a, 0x42, 0x59, 0x2e, 0x30, 0x16, 0xa0, 0xc0, 0xc0, 0xd4, 0x82, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x78, 0x5f, 0x1a, 0x2d,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc0, 0xf3, 0xc0, 0x4c, 0x90,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xac, 0x0c, 0xf0, 0xbc, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0xb4,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x26, 0x43, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x05, 0xb4,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x2d, 0xc3, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x24,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0x36, 0x83, 0x03, 0x3c, 0x2f, 0x48, 0x02, 0x6f, 0xff, 0xd3, 0xee, 0x76, 0xf2, 0x4e, 0x53, 0x5f, 0x1e, 0xbb, 0x79, 0x03, 0x0e, 0xd5, 0x68, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, 0xb4
|
||||||
|
};
|
||||||
@@ -31,6 +31,12 @@ SLVideoDecoder::getDecoderCapabilities()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
SLVideoDecoder::getDecoderColorspace()
|
||||||
|
{
|
||||||
|
return COLORSPACE_REC_709;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
@@ -59,6 +65,7 @@ SLVideoDecoder::initialize(PDECODER_PARAMETERS params)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SLVideo_SetStreamVideoTransferMatrix(m_VideoStream, k_ESLVideoTransferMatrix_BT709);
|
||||||
SLVideo_SetStreamTargetFramerate(m_VideoStream, params->frameRate, 1);
|
SLVideo_SetStreamTargetFramerate(m_VideoStream, params->frameRate, 1);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public:
|
|||||||
virtual bool initialize(PDECODER_PARAMETERS params);
|
virtual bool initialize(PDECODER_PARAMETERS params);
|
||||||
virtual bool isHardwareAccelerated();
|
virtual bool isHardwareAccelerated();
|
||||||
virtual int getDecoderCapabilities();
|
virtual int getDecoderCapabilities();
|
||||||
|
virtual int getDecoderColorspace();
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||||
|
|
||||||
// Unused since rendering is done directly from the decode thread
|
// Unused since rendering is done directly from the decode thread
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.0.0
|
1.2.1
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -162,12 +162,29 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
|
|||||||
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
||||||
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
||||||
#elif defined(__GNUC__) && defined(__arm__)
|
#elif defined(__GNUC__) && defined(__arm__)
|
||||||
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__)
|
#if 0 /* defined(__LINUX__) || defined(__ANDROID__) */
|
||||||
|
/* Information from:
|
||||||
|
https://chromium.googlesource.com/chromium/chromium/+/trunk/base/atomicops_internals_arm_gcc.h#19
|
||||||
|
|
||||||
|
The Linux kernel provides a helper function which provides the right code for a memory barrier,
|
||||||
|
hard-coded at address 0xffff0fa0
|
||||||
|
*/
|
||||||
|
typedef void (*SDL_KernelMemoryBarrierFunc)();
|
||||||
|
#define SDL_MemoryBarrierRelease() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
|
||||||
|
#define SDL_MemoryBarrierAcquire() ((SDL_KernelMemoryBarrierFunc)0xffff0fa0)()
|
||||||
|
#elif 0 /* defined(__QNXNTO__) */
|
||||||
|
#include <sys/cpuinline.h>
|
||||||
|
|
||||||
|
#define SDL_MemoryBarrierRelease() __cpu_membarrier()
|
||||||
|
#define SDL_MemoryBarrierAcquire() __cpu_membarrier()
|
||||||
|
#else
|
||||||
|
#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) || defined(__ARM_ARCH_8A__)
|
||||||
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
||||||
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory")
|
||||||
#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_5TE__)
|
#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_5TE__)
|
||||||
#ifdef __thumb__
|
#ifdef __thumb__
|
||||||
/* The mcr instruction isn't available in thumb mode, use real functions */
|
/* The mcr instruction isn't available in thumb mode, use real functions */
|
||||||
|
#define SDL_MEMORY_BARRIER_USES_FUNCTION
|
||||||
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
|
#define SDL_MemoryBarrierRelease() SDL_MemoryBarrierReleaseFunction()
|
||||||
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
|
#define SDL_MemoryBarrierAcquire() SDL_MemoryBarrierAcquireFunction()
|
||||||
#else
|
#else
|
||||||
@@ -177,6 +194,7 @@ extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquireFunction(void);
|
|||||||
#else
|
#else
|
||||||
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
|
#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory")
|
||||||
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
|
#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory")
|
||||||
|
#endif /* __LINUX__ || __ANDROID__ */
|
||||||
#endif /* __GNUC__ && __arm__ */
|
#endif /* __GNUC__ && __arm__ */
|
||||||
#else
|
#else
|
||||||
#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
|
#if (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5120))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -420,23 +420,56 @@ extern DECLSPEC void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev,
|
|||||||
/* @} *//* Pause audio functions */
|
/* @} *//* Pause audio functions */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function loads a WAVE from the data source, automatically freeing
|
* \brief Load the audio data of a WAVE file into memory
|
||||||
* that source if \c freesrc is non-zero. For example, to load a WAVE file,
|
*
|
||||||
* you could do:
|
* Loading a WAVE file requires \c src, \c spec, \c audio_buf and \c audio_len
|
||||||
|
* to be valid pointers. The entire data portion of the file is then loaded
|
||||||
|
* into memory and decoded if necessary.
|
||||||
|
*
|
||||||
|
* If \c freesrc is non-zero, the data source gets automatically closed and
|
||||||
|
* freed before the function returns.
|
||||||
|
*
|
||||||
|
* Supported are RIFF WAVE files with the formats PCM (8, 16, 24, and 32 bits),
|
||||||
|
* IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and A-law and
|
||||||
|
* µ-law (8 bits). Other formats are currently unsupported and cause an error.
|
||||||
|
*
|
||||||
|
* If this function succeeds, the pointer returned by it is equal to \c spec
|
||||||
|
* and the pointer to the audio data allocated by the function is written to
|
||||||
|
* \c audio_buf and its length in bytes to \c audio_len. The \ref SDL_AudioSpec
|
||||||
|
* members \c freq, \c channels, and \c format are set to the values of the
|
||||||
|
* audio data in the buffer. The \c samples member is set to a sane default and
|
||||||
|
* all others are set to zero.
|
||||||
|
*
|
||||||
|
* It's necessary to use SDL_FreeWAV() to free the audio data returned in
|
||||||
|
* \c audio_buf when it is no longer used.
|
||||||
|
*
|
||||||
|
* Because of the underspecification of the Waveform format, there are many
|
||||||
|
* problematic files in the wild that cause issues with strict decoders. To
|
||||||
|
* provide compatibility with these files, this decoder is lenient in regards
|
||||||
|
* to the truncation of the file, the fact chunk, and the size of the RIFF
|
||||||
|
* chunk. The hints SDL_HINT_WAVE_RIFF_CHUNK_SIZE, SDL_HINT_WAVE_TRUNCATION,
|
||||||
|
* and SDL_HINT_WAVE_FACT_CHUNK can be used to tune the behavior of the
|
||||||
|
* loading process.
|
||||||
|
*
|
||||||
|
* Any file that is invalid (due to truncation, corruption, or wrong values in
|
||||||
|
* the headers), too big, or unsupported causes an error. Additionally, any
|
||||||
|
* critical I/O error from the data source will terminate the loading process
|
||||||
|
* with an error. The function returns NULL on error and in all cases (with the
|
||||||
|
* exception of \c src being NULL), an appropriate error message will be set.
|
||||||
|
*
|
||||||
|
* It is required that the data source supports seeking.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
* \code
|
* \code
|
||||||
* SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...);
|
* SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...);
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* If this function succeeds, it returns the given SDL_AudioSpec,
|
* \param src The data source with the WAVE data
|
||||||
* filled with the audio data format of the wave data, and sets
|
* \param freesrc A integer value that makes the function close the data source if non-zero
|
||||||
* \c *audio_buf to a malloc()'d buffer containing the audio data,
|
* \param spec A pointer filled with the audio format of the audio data
|
||||||
* and sets \c *audio_len to the length of that audio buffer, in bytes.
|
* \param audio_buf A pointer filled with the audio data allocated by the function
|
||||||
* You need to free the audio buffer with SDL_FreeWAV() when you are
|
* \param audio_len A pointer filled with the length of the audio data buffer in bytes
|
||||||
* done with it.
|
* \return NULL on error, or non-NULL on success.
|
||||||
*
|
|
||||||
* This function returns NULL and sets the SDL error message if the
|
|
||||||
* wave file cannot be opened, uses an unknown data format, or is
|
|
||||||
* corrupt. Currently raw and MS-ADPCM WAVE files are supported.
|
|
||||||
*/
|
*/
|
||||||
extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src,
|
extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src,
|
||||||
int freesrc,
|
int freesrc,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -101,6 +101,15 @@ SDL_MostSignificantBitIndex32(Uint32 x)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_FORCE_INLINE SDL_bool
|
||||||
|
SDL_HasExactlyOneBitSet32(Uint32 x)
|
||||||
|
{
|
||||||
|
if (x && !(x & (x - 1))) {
|
||||||
|
return SDL_TRUE;
|
||||||
|
}
|
||||||
|
return SDL_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ends C function definitions when using C++ */
|
/* Ends C function definitions when using C++ */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -90,12 +90,12 @@ typedef enum
|
|||||||
/**
|
/**
|
||||||
* \brief Create a custom blend mode, which may or may not be supported by a given renderer
|
* \brief Create a custom blend mode, which may or may not be supported by a given renderer
|
||||||
*
|
*
|
||||||
* \param srcColorFactor
|
* \param srcColorFactor source color factor
|
||||||
* \param dstColorFactor
|
* \param dstColorFactor destination color factor
|
||||||
* \param colorOperation
|
* \param colorOperation color operation
|
||||||
* \param srcAlphaFactor
|
* \param srcAlphaFactor source alpha factor
|
||||||
* \param dstAlphaFactor
|
* \param dstAlphaFactor destination alpha factor
|
||||||
* \param alphaOperation
|
* \param alphaOperation alpha operation
|
||||||
*
|
*
|
||||||
* The result of the blend mode operation will be:
|
* The result of the blend mode operation will be:
|
||||||
* dstRGB = dstRGB * dstColorFactor colorOperation srcRGB * srcColorFactor
|
* dstRGB = dstRGB * dstColorFactor colorOperation srcRGB * srcColorFactor
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -218,7 +218,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Enable Vulkan support */
|
/* Enable Vulkan support */
|
||||||
/* Metal/MoltenVK/Vulkan only supported on 64-bit architectures with 10.11+ */
|
/* Metal/Vulkan Portability only supported on 64-bit architectures with 10.11+ */
|
||||||
#if TARGET_CPU_X86_64 && (MAC_OS_X_VERSION_MAX_ALLOWED >= 101100)
|
#if TARGET_CPU_X86_64 && (MAC_OS_X_VERSION_MAX_ALLOWED >= 101100)
|
||||||
#define SDL_VIDEO_VULKAN 1
|
#define SDL_VIDEO_VULKAN 1
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -41,24 +41,43 @@
|
|||||||
#else
|
#else
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#ifndef _WIN64
|
#ifndef _WIN64
|
||||||
|
#ifndef __MMX__
|
||||||
#define __MMX__
|
#define __MMX__
|
||||||
|
#endif
|
||||||
|
#ifndef __3dNOW__
|
||||||
#define __3dNOW__
|
#define __3dNOW__
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef __SSE__
|
||||||
#define __SSE__
|
#define __SSE__
|
||||||
|
#endif
|
||||||
|
#ifndef __SSE2__
|
||||||
#define __SSE2__
|
#define __SSE2__
|
||||||
|
#endif
|
||||||
#endif /* __clang__ */
|
#endif /* __clang__ */
|
||||||
#elif defined(__MINGW64_VERSION_MAJOR)
|
#elif defined(__MINGW64_VERSION_MAJOR)
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#else
|
#else
|
||||||
#ifdef __ALTIVEC__
|
/* altivec.h redefining bool causes a number of problems, see bugs 3993 and 4392, so you need to explicitly define SDL_ENABLE_ALTIVEC_H to have it included. */
|
||||||
#if defined(HAVE_ALTIVEC_H) && !defined(__APPLE_ALTIVEC__) && !defined(SDL_DISABLE_ALTIVEC_H)
|
#if defined(HAVE_ALTIVEC_H) && defined(__ALTIVEC__) && !defined(__APPLE_ALTIVEC__) && defined(SDL_ENABLE_ALTIVEC_H)
|
||||||
#include <altivec.h>
|
#include <altivec.h>
|
||||||
#undef pixel
|
|
||||||
#undef bool
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#if !defined(SDL_DISABLE_ARM_NEON_H)
|
||||||
#if defined(__ARM_NEON__) && !defined(SDL_DISABLE_ARM_NEON_H)
|
# if defined(__ARM_NEON)
|
||||||
#include <arm_neon.h>
|
# include <arm_neon.h>
|
||||||
|
# elif defined(__WINDOWS__) || defined(__WINRT__)
|
||||||
|
/* Visual Studio doesn't define __ARM_ARCH, but _M_ARM (if set, always 7), and _M_ARM64 (if set, always 1). */
|
||||||
|
# if defined(_M_ARM)
|
||||||
|
# include <armintr.h>
|
||||||
|
# include <arm_neon.h>
|
||||||
|
# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */
|
||||||
|
# endif
|
||||||
|
# if defined (_M_ARM64)
|
||||||
|
# include <armintr.h>
|
||||||
|
# include <arm_neon.h>
|
||||||
|
# define __ARM_NEON 1 /* Set __ARM_NEON so that it can be used elsewhere, at compile time */
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
|
#if defined(__3dNOW__) && !defined(SDL_DISABLE_MM3DNOW_H)
|
||||||
#include <mm3dnow.h>
|
#include <mm3dnow.h>
|
||||||
@@ -177,6 +196,69 @@ extern DECLSPEC SDL_bool SDLCALL SDL_HasNEON(void);
|
|||||||
*/
|
*/
|
||||||
extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
|
extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Report the alignment this system needs for SIMD allocations.
|
||||||
|
*
|
||||||
|
* This will return the minimum number of bytes to which a pointer must be
|
||||||
|
* aligned to be compatible with SIMD instructions on the current machine.
|
||||||
|
* For example, if the machine supports SSE only, it will return 16, but if
|
||||||
|
* it supports AVX-512F, it'll return 64 (etc). This only reports values for
|
||||||
|
* instruction sets SDL knows about, so if your SDL build doesn't have
|
||||||
|
* SDL_HasAVX512F(), then it might return 16 for the SSE support it sees and
|
||||||
|
* not 64 for the AVX-512 instructions that exist but SDL doesn't know about.
|
||||||
|
* Plan accordingly.
|
||||||
|
*/
|
||||||
|
extern DECLSPEC size_t SDLCALL SDL_SIMDGetAlignment(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Allocate memory in a SIMD-friendly way.
|
||||||
|
*
|
||||||
|
* This will allocate a block of memory that is suitable for use with SIMD
|
||||||
|
* instructions. Specifically, it will be properly aligned and padded for
|
||||||
|
* the system's supported vector instructions.
|
||||||
|
*
|
||||||
|
* The memory returned will be padded such that it is safe to read or write
|
||||||
|
* an incomplete vector at the end of the memory block. This can be useful
|
||||||
|
* so you don't have to drop back to a scalar fallback at the end of your
|
||||||
|
* SIMD processing loop to deal with the final elements without overflowing
|
||||||
|
* the allocated buffer.
|
||||||
|
*
|
||||||
|
* You must free this memory with SDL_FreeSIMD(), not free() or SDL_free()
|
||||||
|
* or delete[], etc.
|
||||||
|
*
|
||||||
|
* Note that SDL will only deal with SIMD instruction sets it is aware of;
|
||||||
|
* for example, SDL 2.0.8 knows that SSE wants 16-byte vectors
|
||||||
|
* (SDL_HasSSE()), and AVX2 wants 32 bytes (SDL_HasAVX2()), but doesn't
|
||||||
|
* know that AVX-512 wants 64. To be clear: if you can't decide to use an
|
||||||
|
* instruction set with an SDL_Has*() function, don't use that instruction
|
||||||
|
* set with memory allocated through here.
|
||||||
|
*
|
||||||
|
* SDL_AllocSIMD(0) will return a non-NULL pointer, assuming the system isn't
|
||||||
|
* out of memory.
|
||||||
|
*
|
||||||
|
* \param len The length, in bytes, of the block to allocated. The actual
|
||||||
|
* allocated block might be larger due to padding, etc.
|
||||||
|
* \return Pointer to newly-allocated block, NULL if out of memory.
|
||||||
|
*
|
||||||
|
* \sa SDL_SIMDAlignment
|
||||||
|
* \sa SDL_SIMDFree
|
||||||
|
*/
|
||||||
|
extern DECLSPEC void * SDLCALL SDL_SIMDAlloc(const size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Deallocate memory obtained from SDL_SIMDAlloc
|
||||||
|
*
|
||||||
|
* It is not valid to use this function on a pointer from anything but
|
||||||
|
* SDL_SIMDAlloc(). It can't be used on pointers from malloc, realloc,
|
||||||
|
* SDL_malloc, memalign, new[], etc.
|
||||||
|
*
|
||||||
|
* However, SDL_SIMDFree(NULL) is a legal no-op.
|
||||||
|
*
|
||||||
|
* \sa SDL_SIMDAlloc
|
||||||
|
*/
|
||||||
|
extern DECLSPEC void SDLCALL SDL_SIMDFree(void *ptr);
|
||||||
|
|
||||||
|
/* vi: set ts=4 sw=4 expandtab: */
|
||||||
/* Ends C function definitions when using C++ */
|
/* Ends C function definitions when using C++ */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -594,6 +594,9 @@ typedef union SDL_Event
|
|||||||
Uint8 padding[56];
|
Uint8 padding[56];
|
||||||
} SDL_Event;
|
} SDL_Event;
|
||||||
|
|
||||||
|
/* Make sure we haven't broken binary compatibility */
|
||||||
|
SDL_COMPILE_TIME_ASSERT(SDL_Event, sizeof(SDL_Event) == 56);
|
||||||
|
|
||||||
|
|
||||||
/* Function prototypes */
|
/* Function prototypes */
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -315,6 +315,16 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_TOUCH_MOUSE_EVENTS "SDL_TOUCH_MOUSE_EVENTS"
|
#define SDL_HINT_TOUCH_MOUSE_EVENTS "SDL_TOUCH_MOUSE_EVENTS"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether mouse events should generate synthetic touch events
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
* "0" - Mouse events will not generate touch events (default for desktop platforms)
|
||||||
|
* "1" - Mouse events will generate touch events (default for mobile platforms, such as Android and iOS)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SDL_HINT_MOUSE_TOUCH_EVENTS "SDL_MOUSE_TOUCH_EVENTS"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to true.
|
* \brief Minimize your SDL_Window if it loses key focus when in fullscreen mode. Defaults to true.
|
||||||
*
|
*
|
||||||
@@ -436,6 +446,16 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG"
|
#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable that lets you provide a file with extra gamecontroller db entries.
|
||||||
|
*
|
||||||
|
* The file should contain lines of gamecontroller config data, see SDL_gamecontroller.h
|
||||||
|
*
|
||||||
|
* This hint must be set before calling SDL_Init(SDL_INIT_GAMECONTROLLER)
|
||||||
|
* You can update mappings after the system is initialized with SDL_GameControllerMappingForGUID() and SDL_GameControllerAddMapping()
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_GAMECONTROLLERCONFIG_FILE "SDL_GAMECONTROLLERCONFIG_FILE"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A variable containing a list of devices to skip when scanning for game controllers.
|
* \brief A variable containing a list of devices to skip when scanning for game controllers.
|
||||||
*
|
*
|
||||||
@@ -836,19 +856,7 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_IME_INTERNAL_EDITING "SDL_IME_INTERNAL_EDITING"
|
#define SDL_HINT_IME_INTERNAL_EDITING "SDL_IME_INTERNAL_EDITING"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A variable to control whether mouse and touch events are to be treated together or separately
|
|
||||||
*
|
|
||||||
* The variable can be set to the following values:
|
|
||||||
* "0" - Mouse events will be handled as touch events, and touch will raise fake mouse
|
|
||||||
* events. This is the behaviour of SDL <= 2.0.3. (default)
|
|
||||||
* "1" - Mouse events will be handled separately from pure touch events.
|
|
||||||
*
|
|
||||||
* The value of this hint is used at runtime, so it can be changed at any time.
|
|
||||||
*/
|
|
||||||
#define SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH "SDL_ANDROID_SEPARATE_MOUSE_AND_TOUCH"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief A variable to control whether we trap the Android back button to handle it manually.
|
* \brief A variable to control whether we trap the Android back button to handle it manually.
|
||||||
* This is necessary for the right mouse button to work on some Android devices, or
|
* This is necessary for the right mouse button to work on some Android devices, or
|
||||||
* to be able to trap the back button for use in your code reliably. If set to true,
|
* to be able to trap the back button for use in your code reliably. If set to true,
|
||||||
@@ -865,6 +873,17 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
|
#define SDL_HINT_ANDROID_TRAP_BACK_BUTTON "SDL_ANDROID_TRAP_BACK_BUTTON"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable to control whether the event loop will block itself when the app is paused.
|
||||||
|
*
|
||||||
|
* The variable can be set to the following values:
|
||||||
|
* "0" - Non blocking.
|
||||||
|
* "1" - Blocking. (default)
|
||||||
|
*
|
||||||
|
* The value should be set before SDL is initialized.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_ANDROID_BLOCK_ON_PAUSE "SDL_ANDROID_BLOCK_ON_PAUSE"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A variable to control whether the return key on the soft keyboard
|
* \brief A variable to control whether the return key on the soft keyboard
|
||||||
* should hide the soft keyboard on Android and iOS.
|
* should hide the soft keyboard on Android and iOS.
|
||||||
@@ -1043,6 +1062,118 @@ extern "C" {
|
|||||||
*/
|
*/
|
||||||
#define SDL_HINT_AUDIO_CATEGORY "SDL_AUDIO_CATEGORY"
|
#define SDL_HINT_AUDIO_CATEGORY "SDL_AUDIO_CATEGORY"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether the 2D render API is compatible or efficient.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
*
|
||||||
|
* "0" - Don't use batching to make rendering more efficient.
|
||||||
|
* "1" - Use batching, but might cause problems if app makes its own direct OpenGL calls.
|
||||||
|
*
|
||||||
|
* Up to SDL 2.0.9, the render API would draw immediately when requested. Now
|
||||||
|
* it batches up draw requests and sends them all to the GPU only when forced
|
||||||
|
* to (during SDL_RenderPresent, when changing render targets, by updating a
|
||||||
|
* texture that the batch needs, etc). This is significantly more efficient,
|
||||||
|
* but it can cause problems for apps that expect to render on top of the
|
||||||
|
* render API's output. As such, SDL will disable batching if a specific
|
||||||
|
* render backend is requested (since this might indicate that the app is
|
||||||
|
* planning to use the underlying graphics API directly). This hint can
|
||||||
|
* be used to explicitly request batching in this instance. It is a contract
|
||||||
|
* that you will either never use the underlying graphics API directly, or
|
||||||
|
* if you do, you will call SDL_RenderFlush() before you do so any current
|
||||||
|
* batch goes to the GPU before your work begins. Not following this contract
|
||||||
|
* will result in undefined behavior.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_RENDER_BATCHING "SDL_RENDER_BATCHING"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A variable controlling whether SDL logs all events pushed onto its internal queue.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
*
|
||||||
|
* "0" - Don't log any events (default)
|
||||||
|
* "1" - Log all events except mouse and finger motion, which are pretty spammy.
|
||||||
|
* "2" - Log all events.
|
||||||
|
*
|
||||||
|
* This is generally meant to be used to debug SDL itself, but can be useful
|
||||||
|
* for application developers that need better visibility into what is going
|
||||||
|
* on in the event queue. Logged events are sent through SDL_Log(), which
|
||||||
|
* means by default they appear on stdout on most platforms or maybe
|
||||||
|
* OutputDebugString() on Windows, and can be funneled by the app with
|
||||||
|
* SDL_LogSetOutputFunction(), etc.
|
||||||
|
*
|
||||||
|
* This hint can be toggled on and off at runtime, if you only need to log
|
||||||
|
* events for a small subset of program execution.
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_EVENT_LOGGING "SDL_EVENT_LOGGING"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Controls how the size of the RIFF chunk affects the loading of a WAVE file.
|
||||||
|
*
|
||||||
|
* The size of the RIFF chunk (which includes all the sub-chunks of the WAVE
|
||||||
|
* file) is not always reliable. In case the size is wrong, it's possible to
|
||||||
|
* just ignore it and step through the chunks until a fixed limit is reached.
|
||||||
|
*
|
||||||
|
* Note that files that have trailing data unrelated to the WAVE file or
|
||||||
|
* corrupt files may slow down the loading process without a reliable boundary.
|
||||||
|
* By default, SDL stops after 10000 chunks to prevent wasting time. Use the
|
||||||
|
* environment variable SDL_WAVE_CHUNK_LIMIT to adjust this value.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
*
|
||||||
|
* "force" - Always use the RIFF chunk size as a boundary for the chunk search
|
||||||
|
* "ignorezero" - Like "force", but a zero size searches up to 4 GiB (default)
|
||||||
|
* "ignore" - Ignore the RIFF chunk size and always search up to 4 GiB
|
||||||
|
* "maximum" - Search for chunks until the end of file (not recommended)
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_WAVE_RIFF_CHUNK_SIZE "SDL_WAVE_RIFF_CHUNK_SIZE"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Controls how a truncated WAVE file is handled.
|
||||||
|
*
|
||||||
|
* A WAVE file is considered truncated if any of the chunks are incomplete or
|
||||||
|
* the data chunk size is not a multiple of the block size. By default, SDL
|
||||||
|
* decodes until the first incomplete block, as most applications seem to do.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
*
|
||||||
|
* "verystrict" - Raise an error if the file is truncated
|
||||||
|
* "strict" - Like "verystrict", but the size of the RIFF chunk is ignored
|
||||||
|
* "dropframe" - Decode until the first incomplete sample frame
|
||||||
|
* "dropblock" - Decode until the first incomplete block (default)
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_WAVE_TRUNCATION "SDL_WAVE_TRUNCATION"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Controls how the fact chunk affects the loading of a WAVE file.
|
||||||
|
*
|
||||||
|
* The fact chunk stores information about the number of samples of a WAVE
|
||||||
|
* file. The Standards Update from Microsoft notes that this value can be used
|
||||||
|
* to 'determine the length of the data in seconds'. This is especially useful
|
||||||
|
* for compressed formats (for which this is a mandatory chunk) if they produce
|
||||||
|
* multiple sample frames per block and truncating the block is not allowed.
|
||||||
|
* The fact chunk can exactly specify how many sample frames there should be
|
||||||
|
* in this case.
|
||||||
|
*
|
||||||
|
* Unfortunately, most application seem to ignore the fact chunk and so SDL
|
||||||
|
* ignores it by default as well.
|
||||||
|
*
|
||||||
|
* This variable can be set to the following values:
|
||||||
|
*
|
||||||
|
* "truncate" - Use the number of samples to truncate the wave data if
|
||||||
|
* the fact chunk is present and valid
|
||||||
|
* "strict" - Like "truncate", but raise an error if the fact chunk
|
||||||
|
* is invalid, not present for non-PCM formats, or if the
|
||||||
|
* data chunk doesn't have that many samples
|
||||||
|
* "ignorezero" - Like "truncate", but ignore fact chunk if the number of
|
||||||
|
* samples is zero
|
||||||
|
* "ignore" - Ignore fact chunk entirely (default)
|
||||||
|
*/
|
||||||
|
#define SDL_HINT_WAVE_FACT_CHUNK "SDL_WAVE_FACT_CHUNK"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief An enumeration of hint priorities
|
* \brief An enumeration of hint priorities
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -55,6 +55,10 @@
|
|||||||
/* On iOS SDL provides a main function that creates an application delegate
|
/* On iOS SDL provides a main function that creates an application delegate
|
||||||
and starts the iOS application run loop.
|
and starts the iOS application run loop.
|
||||||
|
|
||||||
|
If you link with SDL dynamically on iOS, the main function can't be in a
|
||||||
|
shared library, so you need to link with libSDLmain.a, which includes a
|
||||||
|
stub main function that calls into the shared library to start execution.
|
||||||
|
|
||||||
See src/video/uikit/SDL_uikitappdelegate.m for more details.
|
See src/video/uikit/SDL_uikitappdelegate.m for more details.
|
||||||
*/
|
*/
|
||||||
#define SDL_MAIN_NEEDED
|
#define SDL_MAIN_NEEDED
|
||||||
@@ -82,12 +86,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif /* SDL_MAIN_HANDLED */
|
#endif /* SDL_MAIN_HANDLED */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
#define C_LINKAGE "C"
|
|
||||||
#else
|
|
||||||
#define C_LINKAGE
|
|
||||||
#endif /* __cplusplus */
|
|
||||||
|
|
||||||
#ifndef SDLMAIN_DECLSPEC
|
#ifndef SDLMAIN_DECLSPEC
|
||||||
#define SDLMAIN_DECLSPEC
|
#define SDLMAIN_DECLSPEC
|
||||||
#endif
|
#endif
|
||||||
@@ -111,17 +109,18 @@
|
|||||||
#define main SDL_main
|
#define main SDL_main
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* The prototype for the application's main() function
|
|
||||||
*/
|
|
||||||
extern C_LINKAGE SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
|
|
||||||
|
|
||||||
|
|
||||||
#include "begin_code.h"
|
#include "begin_code.h"
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prototype for the application's main() function
|
||||||
|
*/
|
||||||
|
typedef int (*SDL_main_func)(int argc, char *argv[]);
|
||||||
|
extern SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called by the real SDL main function to let the rest of the
|
* This is called by the real SDL main function to let the rest of the
|
||||||
* library know that initialization was done properly.
|
* library know that initialization was done properly.
|
||||||
@@ -136,8 +135,7 @@ extern DECLSPEC void SDLCALL SDL_SetMainReady(void);
|
|||||||
/**
|
/**
|
||||||
* This can be called to set the application class at startup
|
* This can be called to set the application class at startup
|
||||||
*/
|
*/
|
||||||
extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style,
|
extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, void *hInst);
|
||||||
void *hInst);
|
|
||||||
extern DECLSPEC void SDLCALL SDL_UnregisterApp(void);
|
extern DECLSPEC void SDLCALL SDL_UnregisterApp(void);
|
||||||
|
|
||||||
#endif /* __WIN32__ */
|
#endif /* __WIN32__ */
|
||||||
@@ -153,10 +151,24 @@ extern DECLSPEC void SDLCALL SDL_UnregisterApp(void);
|
|||||||
* \return 0 on success, -1 on failure. On failure, use SDL_GetError to retrieve more
|
* \return 0 on success, -1 on failure. On failure, use SDL_GetError to retrieve more
|
||||||
* information on the failure.
|
* information on the failure.
|
||||||
*/
|
*/
|
||||||
extern DECLSPEC int SDLCALL SDL_WinRTRunApp(int (*mainFunction)(int, char **), void * reserved);
|
extern DECLSPEC int SDLCALL SDL_WinRTRunApp(SDL_main_func mainFunction, void * reserved);
|
||||||
|
|
||||||
#endif /* __WINRT__ */
|
#endif /* __WINRT__ */
|
||||||
|
|
||||||
|
#if defined(__IPHONEOS__)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Initializes and launches an SDL application.
|
||||||
|
*
|
||||||
|
* \param argc The argc parameter from the application's main() function
|
||||||
|
* \param argv The argv parameter from the application's main() function
|
||||||
|
* \param mainFunction The SDL app's C-style main().
|
||||||
|
* \return the return value from mainFunction
|
||||||
|
*/
|
||||||
|
extern DECLSPEC int SDLCALL SDL_UIKitRunApp(int argc, char *argv[], SDL_main_func mainFunction);
|
||||||
|
|
||||||
|
#endif /* __IPHONEOS__ */
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Simple DirectMedia Layer
|
Simple DirectMedia Layer
|
||||||
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
warranty. In no event will the authors be held liable for any damages
|
||||||
@@ -168,7 +168,7 @@ enum
|
|||||||
((format) && (SDL_PIXELFLAG(format) != 1))
|
((format) && (SDL_PIXELFLAG(format) != 1))
|
||||||
|
|
||||||
/* Note: If you modify this list, update SDL_GetPixelFormatName() */
|
/* Note: If you modify this list, update SDL_GetPixelFormatName() */
|
||||||
enum
|
typedef enum
|
||||||
{
|
{
|
||||||
SDL_PIXELFORMAT_UNKNOWN,
|
SDL_PIXELFORMAT_UNKNOWN,
|
||||||
SDL_PIXELFORMAT_INDEX1LSB =
|
SDL_PIXELFORMAT_INDEX1LSB =
|
||||||
@@ -290,7 +290,7 @@ enum
|
|||||||
SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1'),
|
SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1'),
|
||||||
SDL_PIXELFORMAT_EXTERNAL_OES = /**< Android video texture format */
|
SDL_PIXELFORMAT_EXTERNAL_OES = /**< Android video texture format */
|
||||||
SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ')
|
SDL_DEFINE_PIXELFOURCC('O', 'E', 'S', ' ')
|
||||||
};
|
} SDL_PixelFormatEnum;
|
||||||
|
|
||||||
typedef struct SDL_Color
|
typedef struct SDL_Color
|
||||||
{
|
{
|
||||||
|
|||||||
Loaded 100 of 313 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user