mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-25 21:40:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
866e2f1762 | ||
|
|
0a705d6752 | ||
|
|
4f74fd5354 | ||
|
|
3df9a5d01c | ||
|
|
2682829bd3 | ||
|
|
30cfa2607d | ||
|
|
4467c2e6ad | ||
|
|
2c0e8a0ddf | ||
|
|
4c17f32a2e | ||
|
|
674220087f | ||
|
|
fc8d5d5799 | ||
|
|
4e4f04c174 | ||
|
|
65c21f3392 | ||
|
|
97fb30cdf1 | ||
|
|
c975279589 | ||
|
|
b3ee7a635f | ||
|
|
c2b12868bb | ||
|
|
bdbb03e16f | ||
|
|
53138d7c16 | ||
|
|
18d1d35104 | ||
|
|
99d9de35de | ||
|
|
e6a48481a5 | ||
|
|
21f2b1224a | ||
|
|
187f47a353 |
No files matched your search
+1
-1
Submodule app/SDL_GameControllerDB updated: cbebd79740...d57f564649.
+2
-4
@@ -261,12 +261,10 @@ macx {
|
|||||||
message(VideoToolbox renderer selected)
|
message(VideoToolbox renderer selected)
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
streaming/video/ffmpeg-renderers/vt.mm \
|
streaming/video/ffmpeg-renderers/vt.mm
|
||||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.mm
|
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
streaming/video/ffmpeg-renderers/vt.h \
|
streaming/video/ffmpeg-renderers/vt.h
|
||||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.h
|
|
||||||
}
|
}
|
||||||
soundio {
|
soundio {
|
||||||
message(libsoundio audio renderer selected)
|
message(libsoundio audio renderer selected)
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ bool NvComputer::wake()
|
|||||||
|
|
||||||
const quint16 WOL_PORTS[] = {
|
const quint16 WOL_PORTS[] = {
|
||||||
7, 9, // Standard WOL ports
|
7, 9, // Standard WOL ports
|
||||||
47998, 47999, 48000, // Ports opened by GFE
|
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the WoL payload
|
// Create the WoL payload
|
||||||
|
|||||||
@@ -33,6 +33,33 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<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">
|
||||||
|
<description>
|
||||||
|
<p>New features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Mouse mode for gamepads (Press and hold Start to toggle)</li>
|
||||||
|
<li>Improved gamepad UI navigation</li>
|
||||||
|
</ul>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed duplicate non-working gamepads appearing on the host</li>
|
||||||
|
<li>Fixed excessive frame queue delay on macOS</li>
|
||||||
|
<li>Fixed excessive audio latency on Steam Link</li>
|
||||||
|
<li>Fixed hiding mouse cursor after streaming on Steam Link</li>
|
||||||
|
<li>Fixed incorrect receive time stats on 32-bit platforms</li>
|
||||||
|
<li>Added UDP ports 48002 and 48010 for Wake-on-LAN</li>
|
||||||
|
<li>Updated included gamepad mappings</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.10.1" date="2019-04-28">
|
<release version="0.10.1" date="2019-04-28">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import QtQuick.Controls 2.2
|
|||||||
|
|
||||||
import AppModel 1.0
|
import AppModel 1.0
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
|
import SdlGamepadKeyNavigation 1.0
|
||||||
|
|
||||||
CenteredGridView {
|
CenteredGridView {
|
||||||
property int computerIndex
|
property int computerIndex
|
||||||
@@ -32,6 +33,11 @@ CenteredGridView {
|
|||||||
StackView.onActivated: {
|
StackView.onActivated: {
|
||||||
appModel.computerLost.connect(computerLost)
|
appModel.computerLost.connect(computerLost)
|
||||||
activated = true
|
activated = true
|
||||||
|
|
||||||
|
// Highlight the first item if a gamepad is connected
|
||||||
|
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
||||||
|
currentIndex = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView.onDeactivating: {
|
StackView.onDeactivating: {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
import SdlGamepadKeyNavigation 1.0
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/45029968/how-do-i-set-the-combobox-width-to-fit-the-largest-item
|
// https://stackoverflow.com/questions/45029968/how-do-i-set-the-combobox-width-to-fit-the-largest-item
|
||||||
ComboBox {
|
ComboBox {
|
||||||
property int textWidth
|
property int textWidth
|
||||||
@@ -28,4 +30,13 @@ ComboBox {
|
|||||||
textWidth = Math.max(popupMetrics.width, textWidth)
|
textWidth = Math.max(popupMetrics.width, textWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
popup.onAboutToShow: {
|
||||||
|
// Switch to normal navigation for combo boxes
|
||||||
|
SdlGamepadKeyNavigation.setUiNavMode(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.onAboutToHide: {
|
||||||
|
SdlGamepadKeyNavigation.setUiNavMode(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import ComputerModel 1.0
|
|||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import StreamingPreferences 1.0
|
import StreamingPreferences 1.0
|
||||||
|
import SdlGamepadKeyNavigation 1.0
|
||||||
|
|
||||||
CenteredGridView {
|
CenteredGridView {
|
||||||
property ComputerModel computerModel : createModel()
|
property ComputerModel computerModel : createModel()
|
||||||
@@ -27,6 +28,16 @@ CenteredGridView {
|
|||||||
StackView.onActivated: {
|
StackView.onActivated: {
|
||||||
// Setup signals on CM
|
// Setup signals on CM
|
||||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||||
|
|
||||||
|
// This is a bit of a hack to do this here as opposed to main.qml, but
|
||||||
|
// we need it enabled before calling getConnectedGamepads() and PcView
|
||||||
|
// is never destroyed, so it should be okay.
|
||||||
|
SdlGamepadKeyNavigation.enable()
|
||||||
|
|
||||||
|
// Highlight the first item if a gamepad is connected
|
||||||
|
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
||||||
|
currentIndex = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView.onDeactivating: {
|
StackView.onDeactivating: {
|
||||||
|
|||||||
@@ -25,11 +25,18 @@ Flickable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StackView.onActivated: {
|
StackView.onActivated: {
|
||||||
SdlGamepadKeyNavigation.setSettingsMode(true)
|
// This enables Tab and BackTab based navigation rather than arrow keys.
|
||||||
|
// It is required to shift focus between controls on the settings page.
|
||||||
|
SdlGamepadKeyNavigation.setUiNavMode(true)
|
||||||
|
|
||||||
|
// Highlight the first item if a gamepad is connected
|
||||||
|
if (SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
||||||
|
resolutionComboBox.forceActiveFocus(Qt.TabFocus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView.onDeactivating: {
|
StackView.onDeactivating: {
|
||||||
SdlGamepadKeyNavigation.setSettingsMode(false)
|
SdlGamepadKeyNavigation.setUiNavMode(false)
|
||||||
|
|
||||||
// Save the prefs so the Session can observe the changes
|
// Save the prefs so the Session can observe the changes
|
||||||
StreamingPreferences.save()
|
StreamingPreferences.save()
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ ApplicationWindow {
|
|||||||
|
|
||||||
visibility: StreamingPreferences.startWindowed ? "Windowed" : "Maximized"
|
visibility: StreamingPreferences.startWindowed ? "Windowed" : "Maximized"
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
SdlGamepadKeyNavigation.enable()
|
|
||||||
}
|
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: stackView
|
id: stackView
|
||||||
initialItem: initialView
|
initialItem: initialView
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
|
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
|
||||||
: m_Enabled(false),
|
: m_Enabled(false),
|
||||||
m_SettingsMode(false),
|
m_UiNavMode(false),
|
||||||
m_LastAxisNavigationEventTime(0)
|
m_LastAxisNavigationEventTime(0)
|
||||||
{
|
{
|
||||||
m_PollingTimer = new QTimer(this);
|
m_PollingTimer = new QTimer(this);
|
||||||
@@ -106,7 +106,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
|
|
||||||
switch (event.cbutton.button) {
|
switch (event.cbutton.button) {
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_UP:
|
case SDL_CONTROLLER_BUTTON_DPAD_UP:
|
||||||
if (m_SettingsMode) {
|
if (m_UiNavMode) {
|
||||||
// Back-tab
|
// Back-tab
|
||||||
sendKey(type, Qt::Key_Tab, Qt::ShiftModifier);
|
sendKey(type, Qt::Key_Tab, Qt::ShiftModifier);
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
||||||
if (m_SettingsMode) {
|
if (m_UiNavMode) {
|
||||||
sendKey(type, Qt::Key_Tab);
|
sendKey(type, Qt::Key_Tab);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -124,24 +124,12 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
|
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
|
||||||
sendKey(type, Qt::Key_Left);
|
sendKey(type, Qt::Key_Left);
|
||||||
if (m_SettingsMode) {
|
|
||||||
// Some settings controls respond to left/right (like the slider)
|
|
||||||
// and others respond to up/down (like combo boxes). They seem to
|
|
||||||
// be mutually exclusive though so let's just send both.
|
|
||||||
sendKey(type, Qt::Key_Up);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
||||||
sendKey(type, Qt::Key_Right);
|
sendKey(type, Qt::Key_Right);
|
||||||
if (m_SettingsMode) {
|
|
||||||
// Some settings controls respond to left/right (like the slider)
|
|
||||||
// and others respond to up/down (like combo boxes). They seem to
|
|
||||||
// be mutually exclusive though so let's just send both.
|
|
||||||
sendKey(type, Qt::Key_Down);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDL_CONTROLLER_BUTTON_A:
|
case SDL_CONTROLLER_BUTTON_A:
|
||||||
if (m_SettingsMode) {
|
if (m_UiNavMode) {
|
||||||
sendKey(type, Qt::Key_Space);
|
sendKey(type, Qt::Key_Space);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -183,7 +171,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
else if (leftY < -30000) {
|
else if (leftY < -30000) {
|
||||||
if (m_SettingsMode) {
|
if (m_UiNavMode) {
|
||||||
// Back-tab
|
// Back-tab
|
||||||
sendKey(QEvent::Type::KeyPress, Qt::Key_Tab, Qt::ShiftModifier);
|
sendKey(QEvent::Type::KeyPress, Qt::Key_Tab, Qt::ShiftModifier);
|
||||||
sendKey(QEvent::Type::KeyRelease, Qt::Key_Tab, Qt::ShiftModifier);
|
sendKey(QEvent::Type::KeyRelease, Qt::Key_Tab, Qt::ShiftModifier);
|
||||||
@@ -196,7 +184,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
m_LastAxisNavigationEventTime = SDL_GetTicks();
|
m_LastAxisNavigationEventTime = SDL_GetTicks();
|
||||||
}
|
}
|
||||||
else if (leftY > 30000) {
|
else if (leftY > 30000) {
|
||||||
if (m_SettingsMode) {
|
if (m_UiNavMode) {
|
||||||
sendKey(QEvent::Type::KeyPress, Qt::Key_Tab);
|
sendKey(QEvent::Type::KeyPress, Qt::Key_Tab);
|
||||||
sendKey(QEvent::Type::KeyRelease, Qt::Key_Tab);
|
sendKey(QEvent::Type::KeyRelease, Qt::Key_Tab);
|
||||||
}
|
}
|
||||||
@@ -210,27 +198,11 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
else if (leftX < -30000) {
|
else if (leftX < -30000) {
|
||||||
sendKey(QEvent::Type::KeyPress, Qt::Key_Left);
|
sendKey(QEvent::Type::KeyPress, Qt::Key_Left);
|
||||||
sendKey(QEvent::Type::KeyRelease, Qt::Key_Left);
|
sendKey(QEvent::Type::KeyRelease, Qt::Key_Left);
|
||||||
if (m_SettingsMode) {
|
|
||||||
// Some settings controls respond to left/right (like the slider)
|
|
||||||
// and others respond to up/down (like combo boxes). They seem to
|
|
||||||
// be mutually exclusive though so let's just send both.
|
|
||||||
sendKey(QEvent::Type::KeyPress, Qt::Key_Up);
|
|
||||||
sendKey(QEvent::Type::KeyRelease, Qt::Key_Up);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_LastAxisNavigationEventTime = SDL_GetTicks();
|
m_LastAxisNavigationEventTime = SDL_GetTicks();
|
||||||
}
|
}
|
||||||
else if (leftX > 30000) {
|
else if (leftX > 30000) {
|
||||||
sendKey(QEvent::Type::KeyPress, Qt::Key_Right);
|
sendKey(QEvent::Type::KeyPress, Qt::Key_Right);
|
||||||
sendKey(QEvent::Type::KeyRelease, Qt::Key_Right);
|
sendKey(QEvent::Type::KeyRelease, Qt::Key_Right);
|
||||||
if (m_SettingsMode) {
|
|
||||||
// Some settings controls respond to left/right (like the slider)
|
|
||||||
// and others respond to up/down (like combo boxes). They seem to
|
|
||||||
// be mutually exclusive though so let's just send both.
|
|
||||||
sendKey(QEvent::Type::KeyPress, Qt::Key_Down);
|
|
||||||
sendKey(QEvent::Type::KeyRelease, Qt::Key_Down);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_LastAxisNavigationEventTime = SDL_GetTicks();
|
m_LastAxisNavigationEventTime = SDL_GetTicks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,9 +218,9 @@ void SdlGamepadKeyNavigation::sendKey(QEvent::Type type, Qt::Key key, Qt::Keyboa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlGamepadKeyNavigation::setSettingsMode(bool settingsMode)
|
void SdlGamepadKeyNavigation::setUiNavMode(bool uiNavMode)
|
||||||
{
|
{
|
||||||
m_SettingsMode = settingsMode;
|
m_UiNavMode = uiNavMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SdlGamepadKeyNavigation::getConnectedGamepads()
|
int SdlGamepadKeyNavigation::getConnectedGamepads()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void disable();
|
Q_INVOKABLE void disable();
|
||||||
|
|
||||||
Q_INVOKABLE void setSettingsMode(bool settingsMode);
|
Q_INVOKABLE void setUiNavMode(bool settingsMode);
|
||||||
|
|
||||||
Q_INVOKABLE int getConnectedGamepads();
|
Q_INVOKABLE int getConnectedGamepads();
|
||||||
|
|
||||||
@@ -32,6 +32,6 @@ private:
|
|||||||
QTimer* m_PollingTimer;
|
QTimer* m_PollingTimer;
|
||||||
QList<SDL_GameController*> m_Gamepads;
|
QList<SDL_GameController*> m_Gamepads;
|
||||||
bool m_Enabled;
|
bool m_Enabled;
|
||||||
bool m_SettingsMode;
|
bool m_UiNavMode;
|
||||||
Uint32 m_LastAxisNavigationEventTime;
|
Uint32 m_LastAxisNavigationEventTime;
|
||||||
};
|
};
|
||||||
@@ -344,6 +344,13 @@ int main(int argc, char *argv[])
|
|||||||
// Disable minimize on focus loss by default. Users seem to want this off by default.
|
// Disable minimize on focus loss by default. Users seem to want this off by default.
|
||||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||||
|
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
|
||||||
|
// when throwing the exceptions, so we don't enable it for release builds out
|
||||||
|
// of caution.
|
||||||
|
SDL_SetHint(SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, "0");
|
||||||
|
#endif
|
||||||
|
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
// After the QGuiApplication is created, the platform stuff will be initialized
|
// After the QGuiApplication is created, the platform stuff will be initialized
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ bool Session::testAudio(int audioConfiguration)
|
|||||||
// the renderer the channel count and sample rate.
|
// the renderer the channel count and sample rate.
|
||||||
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
|
OPUS_MULTISTREAM_CONFIGURATION opusConfig = {};
|
||||||
opusConfig.sampleRate = 48000;
|
opusConfig.sampleRate = 48000;
|
||||||
|
opusConfig.samplesPerFrame = 240;
|
||||||
|
|
||||||
switch (audioConfiguration)
|
switch (audioConfiguration)
|
||||||
{
|
{
|
||||||
@@ -143,25 +144,34 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
s_ActiveSession->m_AudioSampleCount++;
|
s_ActiveSession->m_AudioSampleCount++;
|
||||||
|
|
||||||
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||||
|
int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
|
||||||
|
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
|
||||||
|
if (buffer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
samplesDecoded = opus_multistream_decode(s_ActiveSession->m_OpusDecoder,
|
||||||
(unsigned char*)sampleData,
|
(unsigned char*)sampleData,
|
||||||
sampleLength,
|
sampleLength,
|
||||||
s_ActiveSession->m_OpusDecodeBuffer,
|
(short*)buffer,
|
||||||
SAMPLES_PER_FRAME,
|
desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
|
// Update desiredSize with the number of bytes actually populated by the decoding operation
|
||||||
if (samplesDecoded > 0) {
|
if (samplesDecoded > 0) {
|
||||||
if (!s_ActiveSession->m_AudioRenderer->submitAudio(s_ActiveSession->m_OpusDecodeBuffer,
|
SDL_assert(desiredSize >= sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount);
|
||||||
static_cast<int>(
|
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount;
|
||||||
sizeof(short) *
|
}
|
||||||
samplesDecoded *
|
else {
|
||||||
s_ActiveSession->m_AudioConfig.channelCount))) {
|
desiredSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
if (!s_ActiveSession->m_AudioRenderer->submitAudio(desiredSize)) {
|
||||||
"Reinitializing audio renderer after failure");
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Reinitializing audio renderer after failure");
|
||||||
|
|
||||||
delete s_ActiveSession->m_AudioRenderer;
|
delete s_ActiveSession->m_AudioRenderer;
|
||||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
#define MAX_CHANNELS 6
|
|
||||||
#define SAMPLES_PER_FRAME 240
|
|
||||||
|
|
||||||
class IAudioRenderer
|
class IAudioRenderer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -12,6 +9,8 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig) = 0;
|
||||||
|
|
||||||
|
virtual void* getAudioBuffer(int* size) = 0;
|
||||||
|
|
||||||
// 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(short* audioBuffer, int audioSize) = 0;
|
virtual bool submitAudio(int bytesWritten) = 0;
|
||||||
};
|
};
|
||||||
@@ -12,8 +12,11 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
virtual bool submitAudio(short* audioBuffer, int audioSize);
|
virtual void* getAudioBuffer(int* size);
|
||||||
|
|
||||||
|
virtual bool submitAudio(int bytesWritten);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_AudioDeviceID m_AudioDevice;
|
SDL_AudioDeviceID m_AudioDevice;
|
||||||
|
void* m_AudioBuffer;
|
||||||
};
|
};
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
SdlAudioRenderer::SdlAudioRenderer()
|
SdlAudioRenderer::SdlAudioRenderer()
|
||||||
: m_AudioDevice(0)
|
: m_AudioDevice(0),
|
||||||
|
m_AudioBuffer(nullptr)
|
||||||
{
|
{
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
|
||||||
@@ -30,14 +31,20 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
|||||||
// frames contain a non-power of 2 number of samples,
|
// frames contain a non-power of 2 number of samples,
|
||||||
// so the slop would require buffering another full frame.
|
// so the slop would require buffering another full frame.
|
||||||
// Specifying non-Po2 seems to work for our supported platforms.
|
// Specifying non-Po2 seems to work for our supported platforms.
|
||||||
want.samples = SAMPLES_PER_FRAME;
|
want.samples = opusConfig->samplesPerFrame;
|
||||||
|
|
||||||
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,
|
||||||
"Failed to open audio device: %s",
|
"Failed to open audio device: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_AudioBuffer = malloc(opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount);
|
||||||
|
if (m_AudioBuffer == nullptr) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to allocate audio buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,13 +72,27 @@ SdlAudioRenderer::~SdlAudioRenderer()
|
|||||||
SDL_CloseAudioDevice(m_AudioDevice);
|
SDL_CloseAudioDevice(m_AudioDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_AudioBuffer != nullptr) {
|
||||||
|
free(m_AudioBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
SDL_assert(!SDL_WasInit(SDL_INIT_AUDIO));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SdlAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
void* SdlAudioRenderer::getAudioBuffer(int*)
|
||||||
{
|
{
|
||||||
if (SDL_QueueAudio(m_AudioDevice, audioBuffer, audioSize) < 0) {
|
return m_AudioBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
||||||
|
{
|
||||||
|
if (bytesWritten == 0) {
|
||||||
|
// Nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
|
|||||||
@@ -2,16 +2,10 @@
|
|||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
// To reduce CPU load on the Steam Link, we need to accumulate several frames
|
|
||||||
// before submitting for playback. Higher frames per submission saves more CPU
|
|
||||||
// but increases audio latency.
|
|
||||||
#define FRAMES_PER_SUBMISSION 4
|
|
||||||
|
|
||||||
SLAudioRenderer::SLAudioRenderer()
|
SLAudioRenderer::SLAudioRenderer()
|
||||||
: m_AudioContext(nullptr),
|
: m_AudioContext(nullptr),
|
||||||
m_AudioStream(nullptr),
|
m_AudioStream(nullptr),
|
||||||
m_AudioBuffer(nullptr),
|
m_AudioBuffer(nullptr)
|
||||||
m_AudioBufferBytesFilled(0)
|
|
||||||
{
|
{
|
||||||
SLAudio_SetLogFunction(SLAudioRenderer::slLogCallback, nullptr);
|
SLAudio_SetLogFunction(SLAudioRenderer::slLogCallback, nullptr);
|
||||||
}
|
}
|
||||||
@@ -25,7 +19,12 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_AudioBufferSize = SAMPLES_PER_FRAME * sizeof(short) * opusConfig->channelCount * FRAMES_PER_SUBMISSION;
|
// This number is pretty conservative (especially for surround), but
|
||||||
|
// it's hard to avoid since we get crushed by CPU limitations.
|
||||||
|
m_MaxQueuedAudioMs = 40 * opusConfig->channelCount / 2;
|
||||||
|
|
||||||
|
m_FrameDuration = opusConfig->samplesPerFrame / 48;
|
||||||
|
m_AudioBufferSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||||
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
|
m_AudioStream = SLAudio_CreateStream(m_AudioContext,
|
||||||
opusConfig->sampleRate,
|
opusConfig->sampleRate,
|
||||||
opusConfig->channelCount,
|
opusConfig->channelCount,
|
||||||
@@ -38,17 +37,26 @@ bool SLAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* o
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Using SLAudio renderer");
|
"Using SLAudio renderer with %d ms frames",
|
||||||
|
m_FrameDuration);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void* SLAudioRenderer::getAudioBuffer(int* size)
|
||||||
|
{
|
||||||
|
SDL_assert(*size == m_AudioBufferSize);
|
||||||
|
|
||||||
|
if (m_AudioBuffer == nullptr) {
|
||||||
|
m_AudioBuffer = SLAudio_BeginFrame(m_AudioStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_AudioBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
SLAudioRenderer::~SLAudioRenderer()
|
SLAudioRenderer::~SLAudioRenderer()
|
||||||
{
|
{
|
||||||
if (m_AudioBufferBytesFilled != 0) {
|
if (m_AudioBuffer != nullptr) {
|
||||||
// We had a buffer in flight when we quit. Just in case
|
|
||||||
// SLAudio doesn't handle this properly, we'll zero and submit
|
|
||||||
// it just to be safe.
|
|
||||||
memset(m_AudioBuffer, 0, m_AudioBufferSize);
|
memset(m_AudioBuffer, 0, m_AudioBufferSize);
|
||||||
SLAudio_SubmitFrame(m_AudioStream);
|
SLAudio_SubmitFrame(m_AudioStream);
|
||||||
}
|
}
|
||||||
@@ -62,32 +70,27 @@ SLAudioRenderer::~SLAudioRenderer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SLAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
bool SLAudioRenderer::submitAudio(int bytesWritten)
|
||||||
{
|
{
|
||||||
if (m_AudioBufferBytesFilled == 0) {
|
if (bytesWritten == 0) {
|
||||||
// Get a new audio buffer from SLAudio
|
// This buffer will be reused next time
|
||||||
m_AudioBuffer = (char*)SLAudio_BeginFrame(m_AudioStream);
|
return true;
|
||||||
if (m_AudioBuffer == nullptr) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SLAudio_BeginFrame() failed");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate several frames of audio before submitting to reduce CPU load
|
if (LiGetPendingAudioFrames() * m_FrameDuration < m_MaxQueuedAudioMs) {
|
||||||
SDL_assert(audioSize <= m_AudioBufferSize - m_AudioBufferBytesFilled);
|
|
||||||
memcpy(&m_AudioBuffer[m_AudioBufferBytesFilled], audioBuffer, audioSize);
|
|
||||||
m_AudioBufferBytesFilled += audioSize;
|
|
||||||
|
|
||||||
// Submit the buffer when it's full
|
|
||||||
if (m_AudioBufferBytesFilled == m_AudioBufferSize) {
|
|
||||||
SLAudio_SubmitFrame(m_AudioStream);
|
SLAudio_SubmitFrame(m_AudioStream);
|
||||||
m_AudioBufferBytesFilled = 0;
|
m_AudioBuffer = nullptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Too many queued audio frames: %d",
|
||||||
|
LiGetPendingAudioFrames());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLAudioRenderer::slLogCallback(void *context, ESLAudioLog logLevel, const char *message)
|
void SLAudioRenderer::slLogCallback(void*, ESLAudioLog logLevel, const char *message)
|
||||||
{
|
{
|
||||||
SDL_LogPriority priority;
|
SDL_LogPriority priority;
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,18 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
virtual bool submitAudio(short* audioBuffer, int audioSize);
|
virtual void* getAudioBuffer(int* size);
|
||||||
|
|
||||||
|
virtual bool submitAudio(int bytesWritten);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
|
static void slLogCallback(void* context, ESLAudioLog logLevel, const char* message);
|
||||||
|
|
||||||
CSLAudioContext* m_AudioContext;
|
CSLAudioContext* m_AudioContext;
|
||||||
CSLAudioStream* m_AudioStream;
|
CSLAudioStream* m_AudioStream;
|
||||||
char* m_AudioBuffer;
|
|
||||||
|
void* m_AudioBuffer;
|
||||||
int m_AudioBufferSize;
|
int m_AudioBufferSize;
|
||||||
int m_AudioBufferBytesFilled;
|
int m_FrameDuration;
|
||||||
|
int m_MaxQueuedAudioMs;
|
||||||
};
|
};
|
||||||
@@ -260,7 +260,7 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
|||||||
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 *
|
||||||
m_OpusChannelCount *
|
m_OpusChannelCount *
|
||||||
SAMPLES_PER_FRAME *
|
opusConfig->samplesPerFrame *
|
||||||
packetsToBuffer);
|
packetsToBuffer);
|
||||||
if (m_RingBuffer == nullptr) {
|
if (m_RingBuffer == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -284,15 +284,8 @@ bool SoundIoAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATI
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SoundIoAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
void* SoundIoAudioRenderer::getAudioBuffer(int* size)
|
||||||
{
|
{
|
||||||
if (m_Errored) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush events to update with new device arrivals
|
|
||||||
soundio_flush_events(m_SoundIo);
|
|
||||||
|
|
||||||
// We must always write a full frame of audio. If we don't,
|
// We must always write a full frame of audio. If we don't,
|
||||||
// the reader will get out of sync with the writer and our
|
// the reader will get out of sync with the writer and our
|
||||||
// channels will get all mixed up. To ensure this is always
|
// channels will get all mixed up. To ensure this is always
|
||||||
@@ -300,9 +293,26 @@ bool SoundIoAudioRenderer::submitAudio(short* audioBuffer, int audioSize)
|
|||||||
// of our frame size.
|
// of our frame size.
|
||||||
int bytesFree = soundio_ring_buffer_free_count(m_RingBuffer);
|
int bytesFree = soundio_ring_buffer_free_count(m_RingBuffer);
|
||||||
int bytesPerFrame = m_OpusChannelCount * m_OutputStream->bytes_per_sample;
|
int bytesPerFrame = m_OpusChannelCount * m_OutputStream->bytes_per_sample;
|
||||||
int bytesToWrite = qMin(audioSize, (bytesFree / bytesPerFrame) * bytesPerFrame);
|
*size = qMin(*size, (bytesFree / bytesPerFrame) * bytesPerFrame);
|
||||||
memcpy(soundio_ring_buffer_write_ptr(m_RingBuffer), audioBuffer, bytesToWrite);
|
return soundio_ring_buffer_write_ptr(m_RingBuffer);
|
||||||
soundio_ring_buffer_advance_write_ptr(m_RingBuffer, bytesToWrite);
|
}
|
||||||
|
|
||||||
|
bool SoundIoAudioRenderer::submitAudio(int bytesWritten)
|
||||||
|
{
|
||||||
|
if (m_Errored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesWritten == 0) {
|
||||||
|
// Nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush events to update with new device arrivals
|
||||||
|
soundio_flush_events(m_SoundIo);
|
||||||
|
|
||||||
|
// Advance the write pointer
|
||||||
|
soundio_ring_buffer_advance_write_ptr(m_RingBuffer, bytesWritten);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ public:
|
|||||||
|
|
||||||
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
virtual bool prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|
||||||
virtual bool submitAudio(short* audioBuffer, int audioSize);
|
virtual void* getAudioBuffer(int* size);
|
||||||
|
|
||||||
|
virtual bool submitAudio(int bytesWritten);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
int scoreChannelLayout(const struct SoundIoChannelLayout* layout, const OPUS_MULTISTREAM_CONFIGURATION* opusConfig);
|
||||||
|
|||||||
+147
-2
@@ -5,6 +5,7 @@
|
|||||||
#include "path.h"
|
#include "path.h"
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <QtMath>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
|
||||||
#define VK_0 0x30
|
#define VK_0 0x30
|
||||||
@@ -28,6 +29,18 @@
|
|||||||
// How far the finger can move before it cancels a drag or tap
|
// How far the finger can move before it cancels a drag or tap
|
||||||
#define DEAD_ZONE_DELTA 0.1f
|
#define DEAD_ZONE_DELTA 0.1f
|
||||||
|
|
||||||
|
// How long the Start button must be pressed to toggle mouse emulation
|
||||||
|
#define MOUSE_EMULATION_LONG_PRESS_TIME 750
|
||||||
|
|
||||||
|
// How long between polling the gamepad to send virtual mouse input
|
||||||
|
#define MOUSE_EMULATION_POLLING_INTERVAL 50
|
||||||
|
|
||||||
|
// Determines how fast the mouse will move each interval
|
||||||
|
#define MOUSE_EMULATION_MOTION_MULTIPLIER 4
|
||||||
|
|
||||||
|
// Determines the maximum motion amount before allowing movement
|
||||||
|
#define MOUSE_EMULATION_DEADZONE 2
|
||||||
|
|
||||||
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,
|
||||||
@@ -81,6 +94,12 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||||||
MappingManager mappingManager;
|
MappingManager mappingManager;
|
||||||
mappingManager.applyMappings();
|
mappingManager.applyMappings();
|
||||||
|
|
||||||
|
// Flush gamepad arrival and departure events which may be queued before
|
||||||
|
// starting the gamecontroller subsystem again. This prevents us from
|
||||||
|
// receiving duplicate arrival and departure events for the same gamepad.
|
||||||
|
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
|
||||||
|
SDL_FlushEvent(SDL_CONTROLLERDEVICEREMOVED);
|
||||||
|
|
||||||
// We need to reinit this each time, since you only get
|
// We need to reinit this each time, since you only get
|
||||||
// an initial set of gamepad arrival events once per init.
|
// an initial set of gamepad arrival events once per init.
|
||||||
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
||||||
@@ -115,6 +134,10 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||||||
SdlInputHandler::~SdlInputHandler()
|
SdlInputHandler::~SdlInputHandler()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
||||||
|
if (m_GamepadState[i].mouseEmulationTimer != 0) {
|
||||||
|
Session::get()->notifyMouseEmulationMode(false);
|
||||||
|
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
|
||||||
|
}
|
||||||
if (m_GamepadState[i].haptic != nullptr) {
|
if (m_GamepadState[i].haptic != nullptr) {
|
||||||
SDL_HapticClose(m_GamepadState[i].haptic);
|
SDL_HapticClose(m_GamepadState[i].haptic);
|
||||||
}
|
}
|
||||||
@@ -139,6 +162,14 @@ SdlInputHandler::~SdlInputHandler()
|
|||||||
|
|
||||||
// Return background event handling to off
|
// Return background event handling to off
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
|
||||||
|
|
||||||
|
#ifdef STEAM_LINK
|
||||||
|
// Hide SDL's cursor on Steam Link after quitting the stream.
|
||||||
|
// FIXME: We should also do this for other situations where SDL
|
||||||
|
// and Qt will draw their own mouse cursors like KMSDRM or RPi
|
||||||
|
// video backends.
|
||||||
|
SDL_ShowCursor(SDL_DISABLE);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
|
||||||
@@ -658,6 +689,41 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param)
|
|||||||
return interval;
|
return interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param)
|
||||||
|
{
|
||||||
|
auto gamepad = reinterpret_cast<GamepadState*>(param);
|
||||||
|
|
||||||
|
short rawX;
|
||||||
|
short rawY;
|
||||||
|
|
||||||
|
// Determine which analog stick is currently receiving the strongest input
|
||||||
|
if ((uint32_t)qAbs(gamepad->lsX) + qAbs(gamepad->lsY) > (uint32_t)qAbs(gamepad->rsX) + qAbs(gamepad->rsY)) {
|
||||||
|
rawX = gamepad->lsX;
|
||||||
|
rawY = -gamepad->lsY;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rawX = gamepad->rsX;
|
||||||
|
rawY = -gamepad->rsY;
|
||||||
|
}
|
||||||
|
|
||||||
|
float deltaX;
|
||||||
|
float deltaY;
|
||||||
|
|
||||||
|
// Produce a base vector for mouse movement with increased speed as we deviate further from center
|
||||||
|
deltaX = qPow(rawX / 32766.0f * MOUSE_EMULATION_MOTION_MULTIPLIER, 3);
|
||||||
|
deltaY = qPow(rawY / 32766.0f * MOUSE_EMULATION_MOTION_MULTIPLIER, 3);
|
||||||
|
|
||||||
|
// Enforce deadzones
|
||||||
|
deltaX = qAbs(deltaX) > MOUSE_EMULATION_DEADZONE ? deltaX - MOUSE_EMULATION_DEADZONE : 0;
|
||||||
|
deltaY = qAbs(deltaY) > MOUSE_EMULATION_DEADZONE ? deltaY - MOUSE_EMULATION_DEADZONE : 0;
|
||||||
|
|
||||||
|
if (deltaX != 0 || deltaY != 0) {
|
||||||
|
LiSendMouseMoveEvent((short)deltaX, (short)deltaY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
||||||
{
|
{
|
||||||
SDL_JoystickID gameControllerId = event->which;
|
SDL_JoystickID gameControllerId = event->which;
|
||||||
@@ -716,7 +782,10 @@ void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
|
|||||||
SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERAXISMOTION);
|
SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERAXISMOTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendGamepadState(state);
|
// Only send the gamepad state to the host if it's not in mouse emulation mode
|
||||||
|
if (state->mouseEmulationTimer == 0) {
|
||||||
|
sendGamepadState(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* event)
|
void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* event)
|
||||||
@@ -728,9 +797,76 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
|
|||||||
|
|
||||||
if (event->state == SDL_PRESSED) {
|
if (event->state == SDL_PRESSED) {
|
||||||
state->buttons |= k_ButtonMap[event->button];
|
state->buttons |= k_ButtonMap[event->button];
|
||||||
|
|
||||||
|
if (event->button == SDL_CONTROLLER_BUTTON_START) {
|
||||||
|
state->lastStartDownTime = SDL_GetTicks();
|
||||||
|
}
|
||||||
|
else if (state->mouseEmulationTimer != 0) {
|
||||||
|
if (event->button == SDL_CONTROLLER_BUTTON_A) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_B) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_X) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_MIDDLE);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X1);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X2);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
|
||||||
|
LiSendScrollEvent(1);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
|
||||||
|
LiSendScrollEvent(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
state->buttons &= ~k_ButtonMap[event->button];
|
state->buttons &= ~k_ButtonMap[event->button];
|
||||||
|
|
||||||
|
if (event->button == SDL_CONTROLLER_BUTTON_START) {
|
||||||
|
if (SDL_GetTicks() - state->lastStartDownTime > MOUSE_EMULATION_LONG_PRESS_TIME) {
|
||||||
|
if (state->mouseEmulationTimer != 0) {
|
||||||
|
SDL_RemoveTimer(state->mouseEmulationTimer);
|
||||||
|
state->mouseEmulationTimer = 0;
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Mouse emulation deactivated");
|
||||||
|
Session::get()->notifyMouseEmulationMode(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Send the start button up event to the host, since we won't do it below
|
||||||
|
sendGamepadState(state);
|
||||||
|
|
||||||
|
state->mouseEmulationTimer = SDL_AddTimer(MOUSE_EMULATION_POLLING_INTERVAL, SdlInputHandler::mouseEmulationTimerCallback, state);
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Mouse emulation active");
|
||||||
|
Session::get()->notifyMouseEmulationMode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state->mouseEmulationTimer != 0) {
|
||||||
|
if (event->button == SDL_CONTROLLER_BUTTON_A) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_B) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_X) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_MIDDLE);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X1);
|
||||||
|
}
|
||||||
|
else if (event->button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
|
||||||
|
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Start+Select+L1+R1 as a gamepad quit combo
|
// Handle Start+Select+L1+R1 as a gamepad quit combo
|
||||||
@@ -750,7 +886,10 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendGamepadState(state);
|
// Only send the gamepad state to the host if it's not in mouse emulation mode
|
||||||
|
if (state->mouseEmulationTimer == 0) {
|
||||||
|
sendGamepadState(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* event)
|
||||||
@@ -780,6 +919,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
i = 0;
|
i = 0;
|
||||||
|
|
||||||
for (; i < MAX_GAMEPADS; i++) {
|
for (; i < MAX_GAMEPADS; i++) {
|
||||||
|
SDL_assert(m_GamepadState[i].controller != controller);
|
||||||
if (m_GamepadState[i].controller == NULL) {
|
if (m_GamepadState[i].controller == NULL) {
|
||||||
// Found an empty slot
|
// Found an empty slot
|
||||||
break;
|
break;
|
||||||
@@ -855,6 +995,11 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
|
|||||||
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
state = findStateForGamepad(event->which);
|
state = findStateForGamepad(event->which);
|
||||||
if (state != NULL) {
|
if (state != NULL) {
|
||||||
|
if (state->mouseEmulationTimer != 0) {
|
||||||
|
Session::get()->notifyMouseEmulationMode(false);
|
||||||
|
SDL_RemoveTimer(state->mouseEmulationTimer);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_GameControllerClose(state->controller);
|
SDL_GameControllerClose(state->controller);
|
||||||
if (state->haptic != nullptr) {
|
if (state->haptic != nullptr) {
|
||||||
SDL_HapticClose(state->haptic);
|
SDL_HapticClose(state->haptic);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ struct GamepadState {
|
|||||||
int hapticEffectId;
|
int hapticEffectId;
|
||||||
short index;
|
short index;
|
||||||
|
|
||||||
|
SDL_TimerID mouseEmulationTimer;
|
||||||
|
uint32_t lastStartDownTime;
|
||||||
|
|
||||||
short buttons;
|
short buttons;
|
||||||
short lsX, lsY;
|
short lsX, lsY;
|
||||||
short rsX, rsY;
|
short rsX, rsY;
|
||||||
@@ -82,6 +85,9 @@ private:
|
|||||||
static
|
static
|
||||||
Uint32 mouseMoveTimerCallback(Uint32 interval, void* param);
|
Uint32 mouseMoveTimerCallback(Uint32 interval, void* param);
|
||||||
|
|
||||||
|
static
|
||||||
|
Uint32 mouseEmulationTimerCallback(Uint32 interval, void* param);
|
||||||
|
|
||||||
bool m_MultiController;
|
bool m_MultiController;
|
||||||
SDL_TimerID m_MouseMoveTimer;
|
SDL_TimerID m_MouseMoveTimer;
|
||||||
SDL_atomic_t m_MouseDeltaX;
|
SDL_atomic_t m_MouseDeltaX;
|
||||||
|
|||||||
@@ -49,11 +49,13 @@ AUDIO_RENDERER_CALLBACKS Session::k_AudioCallbacks = {
|
|||||||
nullptr,
|
nullptr,
|
||||||
Session::arCleanup,
|
Session::arCleanup,
|
||||||
Session::arDecodeAndPlaySample,
|
Session::arDecodeAndPlaySample,
|
||||||
CAPABILITY_DIRECT_SUBMIT |
|
#ifndef STEAM_LINK
|
||||||
#ifdef STEAM_LINK
|
CAPABILITY_DIRECT_SUBMIT
|
||||||
CAPABILITY_SLOW_OPUS_DECODER
|
|
||||||
#else
|
#else
|
||||||
0
|
// 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
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -130,6 +132,11 @@ void Session::clConnectionStatusUpdate(int connectionStatus)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s_ActiveSession->m_MouseEmulationRefCount > 0) {
|
||||||
|
// Don't display the overlay if mouse emulation is already using it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (connectionStatus)
|
switch (connectionStatus)
|
||||||
{
|
{
|
||||||
case CONN_STATUS_POOR:
|
case CONN_STATUS_POOR:
|
||||||
@@ -307,6 +314,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||||||
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
||||||
m_InputHandler(nullptr),
|
m_InputHandler(nullptr),
|
||||||
m_InputHandlerLock(0),
|
m_InputHandlerLock(0),
|
||||||
|
m_MouseEmulationRefCount(0),
|
||||||
m_OpusDecoder(nullptr),
|
m_OpusDecoder(nullptr),
|
||||||
m_AudioRenderer(nullptr),
|
m_AudioRenderer(nullptr),
|
||||||
m_AudioSampleCount(0),
|
m_AudioSampleCount(0),
|
||||||
@@ -825,6 +833,22 @@ void Session::toggleFullscreen()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::notifyMouseEmulationMode(bool enabled)
|
||||||
|
{
|
||||||
|
m_MouseEmulationRefCount += enabled ? 1 : -1;
|
||||||
|
SDL_assert(m_MouseEmulationRefCount >= 0);
|
||||||
|
|
||||||
|
// We re-use the status update overlay for mouse mode notification
|
||||||
|
if (m_MouseEmulationRefCount > 0) {
|
||||||
|
strcpy(m_OverlayManager.getOverlayText(Overlay::OverlayStatusUpdate), "Gamepad mouse mode active\nLong press Start to deactivate");
|
||||||
|
m_OverlayManager.setOverlayTextUpdated(Overlay::OverlayStatusUpdate);
|
||||||
|
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Session::exec(int displayOriginX, int displayOriginY)
|
void Session::exec(int displayOriginX, int displayOriginY)
|
||||||
{
|
{
|
||||||
m_DisplayOriginX = displayOriginX;
|
m_DisplayOriginX = displayOriginX;
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ private:
|
|||||||
|
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
|
|
||||||
|
void notifyMouseEmulationMode(bool enabled);
|
||||||
|
|
||||||
void updateOptimalWindowDisplayMode();
|
void updateOptimalWindowDisplayMode();
|
||||||
|
|
||||||
static
|
static
|
||||||
@@ -141,6 +143,7 @@ private:
|
|||||||
bool m_UnexpectedTermination;
|
bool m_UnexpectedTermination;
|
||||||
SdlInputHandler* m_InputHandler;
|
SdlInputHandler* m_InputHandler;
|
||||||
SDL_SpinLock m_InputHandlerLock;
|
SDL_SpinLock m_InputHandlerLock;
|
||||||
|
int m_MouseEmulationRefCount;
|
||||||
|
|
||||||
int m_ActiveVideoFormat;
|
int m_ActiveVideoFormat;
|
||||||
int m_ActiveVideoWidth;
|
int m_ActiveVideoWidth;
|
||||||
@@ -148,7 +151,6 @@ private:
|
|||||||
int m_ActiveVideoFrameRate;
|
int m_ActiveVideoFrameRate;
|
||||||
|
|
||||||
OpusMSDecoder* m_OpusDecoder;
|
OpusMSDecoder* m_OpusDecoder;
|
||||||
short m_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME];
|
|
||||||
IAudioRenderer* m_AudioRenderer;
|
IAudioRenderer* m_AudioRenderer;
|
||||||
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
||||||
int m_AudioSampleCount;
|
int m_AudioSampleCount;
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pacer.h"
|
|
||||||
|
|
||||||
class DisplayLinkVsyncSourceFactory
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static
|
|
||||||
IVsyncSource* createVsyncSource(Pacer* pacer);
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
#include "displaylinkvsyncsource.h"
|
|
||||||
|
|
||||||
#include <SDL_syswm.h>
|
|
||||||
|
|
||||||
#include <CoreVideo/CoreVideo.h>
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
class DisplayLinkVsyncSource : public IVsyncSource
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayLinkVsyncSource(Pacer* pacer)
|
|
||||||
: m_Pacer(pacer),
|
|
||||||
m_DisplayLink(nullptr)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~DisplayLinkVsyncSource() override
|
|
||||||
{
|
|
||||||
if (m_DisplayLink != nullptr) {
|
|
||||||
CVDisplayLinkStop(m_DisplayLink);
|
|
||||||
CVDisplayLinkRelease(m_DisplayLink);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
CGDirectDisplayID
|
|
||||||
getDisplayID(NSScreen* screen)
|
|
||||||
{
|
|
||||||
NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
|
|
||||||
return [screenNumber unsignedIntValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
CVReturn
|
|
||||||
displayLinkOutputCallback(
|
|
||||||
CVDisplayLinkRef displayLink,
|
|
||||||
const CVTimeStamp* /* now */,
|
|
||||||
const CVTimeStamp* /* vsyncTime */,
|
|
||||||
CVOptionFlags,
|
|
||||||
CVOptionFlags*,
|
|
||||||
void *displayLinkContext)
|
|
||||||
{
|
|
||||||
auto me = reinterpret_cast<DisplayLinkVsyncSource*>(displayLinkContext);
|
|
||||||
|
|
||||||
SDL_assert(displayLink == me->m_DisplayLink);
|
|
||||||
|
|
||||||
// In my testing on macOS 10.13, this callback is invoked about 24 ms
|
|
||||||
// prior to the specified v-sync time (now - vsyncTime). Since this is
|
|
||||||
// greater than the standard v-sync interval (16 ms = 60 FPS), we will
|
|
||||||
// draw using the current host time, rather than the actual v-sync target
|
|
||||||
// time. Because the CVDisplayLink is in sync with the actual v-sync
|
|
||||||
// interval, even if many ms prior, we can safely use the current host time
|
|
||||||
// and get a consistent callback for each v-sync. This reduces video latency
|
|
||||||
// by at least 1 frame vs. rendering with the actual vsyncTime.
|
|
||||||
me->m_Pacer->vsyncCallback(500 / me->m_DisplayFps);
|
|
||||||
|
|
||||||
return kCVReturnSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool initialize(SDL_Window* window, int displayFps) override
|
|
||||||
{
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_assert(info.subsystem == SDL_SYSWM_COCOA);
|
|
||||||
|
|
||||||
m_DisplayFps = displayFps;
|
|
||||||
|
|
||||||
NSScreen* screen = [info.info.cocoa.window screen];
|
|
||||||
CVReturn status;
|
|
||||||
if (screen == nullptr) {
|
|
||||||
// Window not visible on any display, so use a
|
|
||||||
// CVDisplayLink that can work with all active displays.
|
|
||||||
// When we become visible, we'll recreate ourselves
|
|
||||||
// and associate with the new screen.
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"NSWindow is not visible on any display");
|
|
||||||
status = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CGDirectDisplayID displayId;
|
|
||||||
displayId = getDisplayID(screen);
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"NSWindow on display: %x",
|
|
||||||
displayId);
|
|
||||||
status = CVDisplayLinkCreateWithCGDisplay(displayId, &m_DisplayLink);
|
|
||||||
}
|
|
||||||
if (status != kCVReturnSuccess) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create CVDisplayLink: %d",
|
|
||||||
status);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
|
||||||
if (status != kCVReturnSuccess) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"CVDisplayLinkSetOutputCallback() failed: %d",
|
|
||||||
status);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = CVDisplayLinkStart(m_DisplayLink);
|
|
||||||
if (status != kCVReturnSuccess) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"CVDisplayLinkStart() failed: %d",
|
|
||||||
status);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Pacer* m_Pacer;
|
|
||||||
CVDisplayLinkRef m_DisplayLink;
|
|
||||||
int m_DisplayFps;
|
|
||||||
};
|
|
||||||
|
|
||||||
IVsyncSource* DisplayLinkVsyncSourceFactory::createVsyncSource(Pacer* pacer) {
|
|
||||||
return new DisplayLinkVsyncSource(pacer);
|
|
||||||
}
|
|
||||||
@@ -61,7 +61,7 @@ bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
|
|||||||
|
|
||||||
m_Window = info.info.win.window;
|
m_Window = info.info.win.window;
|
||||||
|
|
||||||
m_Thread = SDL_CreateThread(vsyncThread, "DX Vsync Thread", this);
|
m_Thread = SDL_CreateThread(vsyncThread, "DXVsync", this);
|
||||||
if (m_Thread == nullptr) {
|
if (m_Thread == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Unable to create DX V-sync thread: %s",
|
"Unable to create DX V-sync thread: %s",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ NullThreadedVsyncSource::~NullThreadedVsyncSource()
|
|||||||
bool NullThreadedVsyncSource::initialize(SDL_Window*, int displayFps)
|
bool NullThreadedVsyncSource::initialize(SDL_Window*, int displayFps)
|
||||||
{
|
{
|
||||||
m_DisplayFps = displayFps;
|
m_DisplayFps = displayFps;
|
||||||
m_Thread = SDL_CreateThread(vsyncThread, "Null Vsync Thread", this);
|
m_Thread = SDL_CreateThread(vsyncThread, "NullVsync", this);
|
||||||
if (m_Thread == nullptr) {
|
if (m_Thread == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Unable to create DX V-sync thread: %s",
|
"Unable to create DX V-sync thread: %s",
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
|
|
||||||
#include "nullthreadedvsyncsource.h"
|
#include "nullthreadedvsyncsource.h"
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
|
||||||
#include "displaylinkvsyncsource.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@@ -230,9 +226,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||||||
"Frame pacing active: target %d Hz with %d FPS stream",
|
"Frame pacing active: target %d Hz with %d FPS stream",
|
||||||
m_DisplayFps, m_MaxVideoFps);
|
m_DisplayFps, m_MaxVideoFps);
|
||||||
|
|
||||||
#if defined(Q_OS_DARWIN)
|
#if defined(Q_OS_WIN32)
|
||||||
m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this);
|
|
||||||
#elif defined(Q_OS_WIN32)
|
|
||||||
// Don't use D3DKMTWaitForVerticalBlankEvent() on Windows 7, because
|
// Don't use D3DKMTWaitForVerticalBlankEvent() on Windows 7, because
|
||||||
// it blocks during other concurrent DX operations (like actually rendering).
|
// it blocks during other concurrent DX operations (like actually rendering).
|
||||||
if (IsWindows8OrGreater()) {
|
if (IsWindows8OrGreater()) {
|
||||||
@@ -254,7 +248,7 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_VsyncRenderer->isRenderThreadSupported()) {
|
if (m_VsyncRenderer->isRenderThreadSupported()) {
|
||||||
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "Pacer Render Thread", this);
|
m_RenderThread = SDL_CreateThread(Pacer::renderThread, "PacerRender", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
const std::vector<int> SdlRenderer::k_SwFormats({
|
const std::vector<int> SdlRenderer::k_SwFormats({
|
||||||
AV_PIX_FMT_YUV420P,
|
AV_PIX_FMT_YUV420P,
|
||||||
AV_PIX_FMT_NV12
|
AV_PIX_FMT_NV12,
|
||||||
|
AV_PIX_FMT_NV21
|
||||||
});
|
});
|
||||||
|
|
||||||
SdlRenderer::SdlRenderer()
|
SdlRenderer::SdlRenderer()
|
||||||
@@ -285,6 +286,9 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
|||||||
case AV_PIX_FMT_NV12:
|
case AV_PIX_FMT_NV12:
|
||||||
sdlFormat = SDL_PIXELFORMAT_NV12;
|
sdlFormat = SDL_PIXELFORMAT_NV12;
|
||||||
break;
|
break;
|
||||||
|
case AV_PIX_FMT_NV21:
|
||||||
|
sdlFormat = SDL_PIXELFORMAT_NV21;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
SDL_assert(false);
|
SDL_assert(false);
|
||||||
goto Exit;
|
goto Exit;
|
||||||
|
|||||||
@@ -22,13 +22,29 @@ public:
|
|||||||
: m_HwContext(nullptr),
|
: m_HwContext(nullptr),
|
||||||
m_DisplayLayer(nullptr),
|
m_DisplayLayer(nullptr),
|
||||||
m_FormatDesc(nullptr),
|
m_FormatDesc(nullptr),
|
||||||
m_StreamView(nullptr)
|
m_StreamView(nullptr),
|
||||||
|
m_DisplayLink(nullptr),
|
||||||
|
m_VsyncMutex(nullptr),
|
||||||
|
m_VsyncPassed(nullptr)
|
||||||
{
|
{
|
||||||
SDL_zero(m_OverlayTextFields);
|
SDL_zero(m_OverlayTextFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~VTRenderer() override
|
virtual ~VTRenderer() override
|
||||||
{
|
{
|
||||||
|
if (m_DisplayLink != nullptr) {
|
||||||
|
CVDisplayLinkStop(m_DisplayLink);
|
||||||
|
CVDisplayLinkRelease(m_DisplayLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_VsyncPassed != nullptr) {
|
||||||
|
SDL_DestroyCond(m_VsyncPassed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_VsyncMutex != nullptr) {
|
||||||
|
SDL_DestroyMutex(m_VsyncMutex);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_HwContext != nullptr) {
|
if (m_HwContext != nullptr) {
|
||||||
av_buffer_unref(&m_HwContext);
|
av_buffer_unref(&m_HwContext);
|
||||||
}
|
}
|
||||||
@@ -48,6 +64,78 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
CVReturn
|
||||||
|
displayLinkOutputCallback(
|
||||||
|
CVDisplayLinkRef displayLink,
|
||||||
|
const CVTimeStamp* /* now */,
|
||||||
|
const CVTimeStamp* /* vsyncTime */,
|
||||||
|
CVOptionFlags,
|
||||||
|
CVOptionFlags*,
|
||||||
|
void *displayLinkContext)
|
||||||
|
{
|
||||||
|
auto me = reinterpret_cast<VTRenderer*>(displayLinkContext);
|
||||||
|
|
||||||
|
SDL_assert(displayLink == me->m_DisplayLink);
|
||||||
|
|
||||||
|
SDL_LockMutex(me->m_VsyncMutex);
|
||||||
|
SDL_CondSignal(me->m_VsyncPassed);
|
||||||
|
SDL_UnlockMutex(me->m_VsyncMutex);
|
||||||
|
|
||||||
|
return kCVReturnSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initializeVsyncCallback(SDL_SysWMinfo* info)
|
||||||
|
{
|
||||||
|
NSScreen* screen = [info->info.cocoa.window screen];
|
||||||
|
CVReturn status;
|
||||||
|
if (screen == nullptr) {
|
||||||
|
// Window not visible on any display, so use a
|
||||||
|
// CVDisplayLink that can work with all active displays.
|
||||||
|
// When we become visible, we'll recreate ourselves
|
||||||
|
// and associate with the new screen.
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"NSWindow is not visible on any display");
|
||||||
|
status = CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CGDirectDisplayID displayId = [[screen deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"NSWindow on display: %x",
|
||||||
|
displayId);
|
||||||
|
status = CVDisplayLinkCreateWithCGDisplay(displayId, &m_DisplayLink);
|
||||||
|
}
|
||||||
|
if (status != kCVReturnSuccess) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to create CVDisplayLink: %d",
|
||||||
|
status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||||
|
if (status != kCVReturnSuccess) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CVDisplayLinkSetOutputCallback() failed: %d",
|
||||||
|
status);
|
||||||
|
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);
|
||||||
|
if (status != kCVReturnSuccess) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"CVDisplayLinkStart() failed: %d",
|
||||||
|
status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Caller frees frame after we return
|
// Caller frees frame after we return
|
||||||
virtual void renderFrame(AVFrame* frame) override
|
virtual void renderFrame(AVFrame* frame) override
|
||||||
{
|
{
|
||||||
@@ -104,6 +192,16 @@ public:
|
|||||||
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
||||||
|
|
||||||
CFRelease(sampleBuffer);
|
CFRelease(sampleBuffer);
|
||||||
|
|
||||||
|
if (m_DisplayLink != nullptr) {
|
||||||
|
// Vsync is enabled, so wait for a swap before returning
|
||||||
|
SDL_LockMutex(m_VsyncMutex);
|
||||||
|
if (SDL_CondWaitTimeout(m_VsyncPassed, m_VsyncMutex, 100) == SDL_MUTEX_TIMEDOUT) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"V-sync wait timed out after 100 ms");
|
||||||
|
}
|
||||||
|
SDL_UnlockMutex(m_VsyncMutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool initialize(PDECODER_PARAMETERS params) override
|
virtual bool initialize(PDECODER_PARAMETERS params) override
|
||||||
@@ -191,6 +289,12 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params->enableVsync) {
|
||||||
|
if (!initializeVsyncCallback(&info)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,20 +382,15 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual IFFmpegRenderer::FramePacingConstraint getFramePacingConstraint() override
|
|
||||||
{
|
|
||||||
// This renderer is inherently tied to V-sync due how we're
|
|
||||||
// rendering with AVSampleBufferDisplay layer. Running without
|
|
||||||
// the V-Sync source leads to massive stuttering.
|
|
||||||
return PACING_FORCE_ON;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AVBufferRef* m_HwContext;
|
AVBufferRef* m_HwContext;
|
||||||
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||||
CMVideoFormatDescriptionRef m_FormatDesc;
|
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||||
NSView* m_StreamView;
|
NSView* m_StreamView;
|
||||||
NSTextField* m_OverlayTextFields[Overlay::OverlayMax];
|
NSTextField* m_OverlayTextFields[Overlay::OverlayMax];
|
||||||
|
CVDisplayLinkRef m_DisplayLink;
|
||||||
|
SDL_mutex* m_VsyncMutex;
|
||||||
|
SDL_cond* m_VsyncPassed;
|
||||||
};
|
};
|
||||||
|
|
||||||
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
||||||
|
|||||||
@@ -620,9 +620,9 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
// Add some extra space in case we need to do an SPS fixup
|
// Add some extra space in case we need to do an SPS fixup
|
||||||
requiredBufferSize += MAX_SPS_EXTRA_SIZE;
|
requiredBufferSize += MAX_SPS_EXTRA_SIZE;
|
||||||
}
|
}
|
||||||
if (requiredBufferSize + AV_INPUT_BUFFER_PADDING_SIZE > m_DecodeBuffer.length()) {
|
|
||||||
m_DecodeBuffer = QByteArray(requiredBufferSize + AV_INPUT_BUFFER_PADDING_SIZE, 0);
|
// Ensure the decoder buffer is large enough
|
||||||
}
|
m_DecodeBuffer.reserve(requiredBufferSize + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||||
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
while (entry != nullptr) {
|
while (entry != nullptr) {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ SLVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
|||||||
return DR_OK;
|
return DR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLVideoDecoder::slLogCallback(void *context, ESLVideoLog logLevel, const char *message)
|
void SLVideoDecoder::slLogCallback(void*, ESLVideoLog logLevel, const char *message)
|
||||||
{
|
{
|
||||||
SDL_LogPriority priority;
|
SDL_LogPriority priority;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
0.10.1
|
1.0.1
|
||||||
Submodule moonlight-common-c/moonlight-common-c updated: 4a82762118...801aaf43d6.
Reference in new issue
Block a user