Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb53c95207 | ||
|
|
960e083152 | ||
|
|
7e560aa09e | ||
|
|
7d9478b774 | ||
|
|
0fa0ab43c9 | ||
|
|
f9cdd49d2e | ||
|
|
7661f5a3d7 | ||
|
|
395f337bbb | ||
|
|
9e9fe2a2cf | ||
|
|
5bf88cfe96 | ||
|
|
f7d3c10c9d | ||
|
|
6b395c816f | ||
|
|
d6e7173af0 | ||
|
|
1a60484abc | ||
|
|
62f765b2b4 | ||
|
|
57f2fb07f0 | ||
|
|
1b8c3aae73 | ||
|
|
845e84adb7 | ||
|
|
91c0429e2c | ||
|
|
30f673efe1 | ||
|
|
c1b1719914 | ||
|
|
9fbdd794b9 | ||
|
|
38ff2bf5cb | ||
|
|
345e800abd | ||
|
|
404eaa44e4 | ||
|
|
8e257ab241 | ||
|
|
fabbad5370 | ||
|
|
7d61acb2a8 | ||
|
|
f6a451d6e7 | ||
|
|
e68a15c825 | ||
|
|
f929cffce7 | ||
|
|
f171588616 | ||
|
|
e467ce7ce8 | ||
|
|
1fda7550da | ||
|
|
f714a5d0cb | ||
|
|
2a3d5a27a8 | ||
|
|
c9bf5ae79d | ||
|
|
cf2d8f71d6 | ||
|
|
611d9ac1ba | ||
|
|
f3309087ab |
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us address broken functionality
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to reproduce**
|
||||
Any special steps that are required for the bug to appear.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem. If the issue is related to video glitching or poor quality, please include screenshots.
|
||||
|
||||
**Moonlight settings (please complete the following information)**
|
||||
- Have any settings been adjusted from defaults?
|
||||
- If so, which settings have been changed?
|
||||
- Does the problem still occur after reverting settings back to default?
|
||||
|
||||
**Client PC details (please complete the following information)**
|
||||
- OS: [e.g. Windows 10 1803]
|
||||
- Moonlight Version: [e.g. v0.2.0]
|
||||
|
||||
**Logs (please attach)**
|
||||
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
|
||||
- On macOS, `Moonlight-###.log` files can be found in `/tmp`. In Finder, press Cmd+Shift+G, then type `/tmp` to navigate there.
|
||||
- On Linux, logs print to the terminal when running via `flatpak run com.moonlight_stream.Moonlight`
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
+2
-2
@@ -23,9 +23,9 @@
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.2.0</string>
|
||||
<string>0.3.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.0</string>
|
||||
<string>0.3.0</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Moonlight</string>
|
||||
</dict>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<description>Moonlight Game Streaming</description>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
+31
-11
@@ -34,7 +34,7 @@ win32 {
|
||||
LIBS += -L$$PWD/../libs/windows/lib/x64
|
||||
}
|
||||
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib
|
||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib
|
||||
}
|
||||
macx {
|
||||
INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
||||
@@ -88,7 +88,8 @@ SOURCES += \
|
||||
gui/computermodel.cpp \
|
||||
gui/appmodel.cpp \
|
||||
streaming/streamutils.cpp \
|
||||
backend/autoupdatechecker.cpp
|
||||
backend/autoupdatechecker.cpp \
|
||||
path.cpp
|
||||
|
||||
HEADERS += \
|
||||
utils.h \
|
||||
@@ -104,7 +105,8 @@ HEADERS += \
|
||||
gui/appmodel.h \
|
||||
streaming/video/decoder.h \
|
||||
streaming/streamutils.h \
|
||||
backend/autoupdatechecker.h
|
||||
backend/autoupdatechecker.h \
|
||||
path.h
|
||||
|
||||
# Platform-specific renderers and decoders
|
||||
ffmpeg {
|
||||
@@ -113,10 +115,16 @@ ffmpeg {
|
||||
DEFINES += HAVE_FFMPEG
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg.cpp \
|
||||
streaming/video/ffmpeg-renderers/sdl.cpp
|
||||
streaming/video/ffmpeg-renderers/sdl.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.cpp
|
||||
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg.h \
|
||||
streaming/video/ffmpeg-renderers/renderer.h
|
||||
streaming/video/ffmpeg-renderers/renderer.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/pacer.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/nullthreadedvsyncsource.h
|
||||
}
|
||||
libva {
|
||||
message(VAAPI renderer selected)
|
||||
@@ -156,14 +164,24 @@ config_SLVideo {
|
||||
win32 {
|
||||
message(DXVA2 renderer selected)
|
||||
|
||||
SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp
|
||||
HEADERS += streaming/video/ffmpeg-renderers/dxva2.h
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/dxva2.cpp \
|
||||
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/dxva2.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h
|
||||
}
|
||||
macx {
|
||||
message(VideoToolbox renderer selected)
|
||||
|
||||
SOURCES += streaming/video/ffmpeg-renderers/vt.mm
|
||||
HEADERS += streaming/video/ffmpeg-renderers/vt.h
|
||||
SOURCES += \
|
||||
streaming/video/ffmpeg-renderers/vt.mm \
|
||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.cpp
|
||||
|
||||
HEADERS += \
|
||||
streaming/video/ffmpeg-renderers/vt.h \
|
||||
streaming/video/ffmpeg-renderers/pacer/displaylinkvsyncsource.h
|
||||
}
|
||||
|
||||
RESOURCES += \
|
||||
@@ -213,6 +231,8 @@ unix:!macx: {
|
||||
}
|
||||
win32 {
|
||||
RC_ICONS = moonlight.ico
|
||||
CONFIG -= embed_manifest_exe
|
||||
QMAKE_LFLAGS += /MANIFEST:embed /MANIFESTINPUT:$${PWD}/Moonlight.exe.manifest
|
||||
}
|
||||
macx {
|
||||
QMAKE_INFO_PLIST = $$PWD/Info.plist
|
||||
@@ -222,5 +242,5 @@ macx {
|
||||
QMAKE_BUNDLE_DATA += APP_BUNDLE_RESOURCES
|
||||
}
|
||||
|
||||
VERSION = 0.2.0
|
||||
DEFINES += VERSION_STR=\\\"0.2.0\\\"
|
||||
VERSION = 0.3.0
|
||||
DEFINES += VERSION_STR=\\\"0.3.0\\\"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#include "boxartmanager.h"
|
||||
#include "../path.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QImageReader>
|
||||
#include <QImageWriter>
|
||||
|
||||
BoxArtManager::BoxArtManager(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_BoxArtDir(
|
||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart")
|
||||
m_BoxArtDir(Path::getBoxArtCacheDir())
|
||||
{
|
||||
if (!m_BoxArtDir.exists()) {
|
||||
m_BoxArtDir.mkpath(".");
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
NvHTTP::NvHTTP(QString address) :
|
||||
m_Address(address)
|
||||
{
|
||||
Q_ASSERT(!address.isEmpty());
|
||||
|
||||
m_BaseUrlHttp.setScheme("http");
|
||||
m_BaseUrlHttps.setScheme("https");
|
||||
m_BaseUrlHttp.setHost(address);
|
||||
|
||||
@@ -33,6 +33,15 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="0.3.0" date="2018-08-21">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
<ul>
|
||||
<li>Reduced power consumption when the app is minimized</li>
|
||||
<li>Fixed malformed asset requests being sent when the first host in the grid is offline</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.2.0" date="2018-08-12">
|
||||
<description>
|
||||
<p>Bugfixes:</p>
|
||||
|
||||
+7
-16
@@ -36,15 +36,9 @@ GridView {
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Start polling when this view is shown
|
||||
ComputerManager.startPolling()
|
||||
|
||||
appModel.computerLost.connect(computerLost)
|
||||
}
|
||||
else {
|
||||
// Stop polling when this view is not on top
|
||||
ComputerManager.stopPollingAsync()
|
||||
|
||||
appModel.computerLost.disconnect(computerLost)
|
||||
}
|
||||
}
|
||||
@@ -106,9 +100,7 @@ GridView {
|
||||
}
|
||||
|
||||
var component = Qt.createComponent("StreamSegue.qml")
|
||||
var segue = component.createObject(stackView)
|
||||
segue.appName = model.name
|
||||
segue.session = appModel.createSessionForApp(index)
|
||||
var segue = component.createObject(stackView, {"appName": model.name, "session": appModel.createSessionForApp(index)})
|
||||
stackView.push(segue)
|
||||
}
|
||||
|
||||
@@ -121,20 +113,19 @@ GridView {
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: {
|
||||
var component = Qt.createComponent("QuitSegue.qml")
|
||||
var segue = component.createObject(stackView)
|
||||
segue.appName = appName
|
||||
var params = {"appName": appName}
|
||||
if (segueToStream) {
|
||||
// Store the session and app name if we're going to stream after
|
||||
// successfully quitting the old app.
|
||||
segue.nextAppName = model.name
|
||||
segue.nextSession = appModel.createSessionForApp(index)
|
||||
params.nextAppName = model.name
|
||||
params.nextSession = appModel.createSessionForApp(index)
|
||||
}
|
||||
else {
|
||||
segue.nextAppName = null
|
||||
segue.nextSession = null
|
||||
params.nextAppName = null
|
||||
params.nextSession = null
|
||||
}
|
||||
|
||||
stackView.push(segue)
|
||||
stackView.push(component.createObject(stackView, params))
|
||||
|
||||
// Trigger the quit after pushing the quit segue on screen
|
||||
appModel.quitRunningApp()
|
||||
|
||||
+2
-20
@@ -2,6 +2,7 @@ import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import ComputerModel 1.0
|
||||
|
||||
@@ -42,25 +43,11 @@ GridView {
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
// Start polling when this view is shown
|
||||
ComputerManager.startPolling()
|
||||
}
|
||||
else {
|
||||
// Stop polling when this view is not top-most
|
||||
ComputerManager.stopPollingAsync()
|
||||
}
|
||||
}
|
||||
|
||||
function pairingComplete(error)
|
||||
{
|
||||
// Close the PIN dialog
|
||||
pairDialog.close()
|
||||
|
||||
// Start polling again
|
||||
ComputerManager.startPolling()
|
||||
|
||||
// Display a failed dialog if we got an error
|
||||
if (error !== null) {
|
||||
errorDialog.text = error
|
||||
@@ -167,18 +154,13 @@ GridView {
|
||||
if (model.paired) {
|
||||
// go to game view
|
||||
var component = Qt.createComponent("AppView.qml")
|
||||
var appView = component.createObject(stackView)
|
||||
appView.computerIndex = index
|
||||
appView.objectName = model.name
|
||||
var appView = component.createObject(stackView, {"computerIndex": index, "objectName": model.name})
|
||||
stackView.push(appView)
|
||||
}
|
||||
else {
|
||||
if (!model.busy) {
|
||||
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
|
||||
|
||||
// Stop polling, since pairing may make GFE unresponsive
|
||||
ComputerManager.stopPollingAsync()
|
||||
|
||||
// Kick off pairing in the background
|
||||
computerModel.pairComputer(index, pin)
|
||||
|
||||
|
||||
@@ -33,9 +33,7 @@ Item {
|
||||
// If we're supposed to launch another game after this, do so now
|
||||
if (error === undefined && nextSession !== null) {
|
||||
var component = Qt.createComponent("StreamSegue.qml")
|
||||
var segue = component.createObject(stackView)
|
||||
segue.appName = nextAppName
|
||||
segue.session = nextSession
|
||||
var segue = component.createObject(stackView, {"appName": nextAppName, "session": nextSession})
|
||||
stackView.push(segue)
|
||||
}
|
||||
}
|
||||
@@ -44,16 +42,10 @@ Item {
|
||||
if (visible) {
|
||||
// Connect the quit completion signal
|
||||
ComputerManager.quitAppCompleted.connect(quitAppCompleted)
|
||||
|
||||
// We must be polling or we won't get the quitAppCompleted() callback
|
||||
ComputerManager.startPolling()
|
||||
}
|
||||
else {
|
||||
// Disconnect the signal
|
||||
ComputerManager.quitAppCompleted.disconnect(quitAppCompleted)
|
||||
|
||||
// Stop polling when this view is not on top
|
||||
ComputerManager.stopPollingAsync()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,13 +230,26 @@ ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: fullScreenCheck
|
||||
text: "<font color=\"white\">Full-screen</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.fullScreen
|
||||
onCheckedChanged: {
|
||||
prefs.fullScreen = checked
|
||||
Row {
|
||||
CheckBox {
|
||||
id: fullScreenCheck
|
||||
text: "<font color=\"white\">Full-screen</font>"
|
||||
font.pointSize: 12
|
||||
checked: prefs.fullScreen
|
||||
onCheckedChanged: {
|
||||
prefs.fullScreen = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: vsyncCheck
|
||||
text: "<font color=\"white\">Enable V-Sync</font>"
|
||||
font.pointSize: 12
|
||||
visible: fullScreenCheck.checked
|
||||
checked: prefs.enableVsync
|
||||
onCheckedChanged: {
|
||||
prefs.enableVsync = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import QtQuick.Controls.Material 2.1
|
||||
|
||||
import ComputerManager 1.0
|
||||
import AutoUpdateChecker 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
property bool pollingActive: false
|
||||
|
||||
id: window
|
||||
visible: true
|
||||
width: 1280
|
||||
@@ -21,6 +25,24 @@ ApplicationWindow {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
onVisibilityChanged: {
|
||||
// We don't want to just use 'active' here because that will stop polling if
|
||||
// we lose focus, which might be overzealous for users with multiple screens
|
||||
// where we may be clearly visible on the other display. Ideally we'll poll
|
||||
// only if the window is visible to the user (not if obscured by other windows),
|
||||
// but it seems difficult to do this portably.
|
||||
var shouldPoll = visibility !== Window.Minimized && visibility !== Window.Hidden
|
||||
|
||||
if (shouldPoll && !pollingActive) {
|
||||
ComputerManager.startPolling()
|
||||
pollingActive = true
|
||||
}
|
||||
else if (!shouldPoll && pollingActive) {
|
||||
ComputerManager.stopPollingAsync()
|
||||
pollingActive = false
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(url, objectName)
|
||||
{
|
||||
var existingItem = stackView.find(function(item, index) {
|
||||
|
||||
+21
-7
@@ -11,6 +11,7 @@
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
|
||||
#include "path.h"
|
||||
#include "gui/computermodel.h"
|
||||
#include "gui/appmodel.h"
|
||||
#include "backend/autoupdatechecker.h"
|
||||
@@ -130,15 +131,23 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (QFile(QDir::currentPath() + "/portable.dat").exists()) {
|
||||
qInfo() << "Running in portable mode from:" << QDir::currentPath();
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QDir::currentPath());
|
||||
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QDir::currentPath());
|
||||
|
||||
// Initialize paths for portable mode
|
||||
Path::initialize(true);
|
||||
}
|
||||
else {
|
||||
// Initialize paths for standard installation
|
||||
Path::initialize(false);
|
||||
}
|
||||
|
||||
#ifdef USE_CUSTOM_LOGGER
|
||||
#ifdef LOG_TO_FILE
|
||||
#ifdef Q_OS_DARWIN
|
||||
// On macOS, $TMPDIR is some random folder under /var/folders/ that nobody can
|
||||
// easily find, so use the system's global tmp directory instead.
|
||||
QDir tempDir("/tmp");
|
||||
#else
|
||||
QDir tempDir(QDir::tempPath());
|
||||
#endif
|
||||
QDir tempDir(Path::getLogDir());
|
||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||
if (s_LoggerFile->open(QIODevice::WriteOnly)) {
|
||||
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
|
||||
@@ -200,6 +209,11 @@ int main(int argc, char *argv[])
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
// Avoid the default behavior of changing the timer resolution to 1 ms.
|
||||
// We don't want this all the time that Moonlight is open. We will set
|
||||
// it manually when we start streaming.
|
||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
||||
|
||||
int err = app.exec();
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "path.h"
|
||||
|
||||
#include <QtDebug>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
|
||||
QString Path::s_LogDir;
|
||||
QString Path::s_BoxArtCacheDir;
|
||||
|
||||
QString Path::getLogDir()
|
||||
{
|
||||
Q_ASSERT(!s_LogDir.isEmpty());
|
||||
return s_LogDir;
|
||||
}
|
||||
|
||||
QString Path::getBoxArtCacheDir()
|
||||
{
|
||||
Q_ASSERT(!s_BoxArtCacheDir.isEmpty());
|
||||
return s_BoxArtCacheDir;
|
||||
}
|
||||
|
||||
void Path::initialize(bool portable)
|
||||
{
|
||||
if (portable) {
|
||||
s_LogDir = QDir::currentPath();
|
||||
s_BoxArtCacheDir = QDir::currentPath() + "/boxart";
|
||||
}
|
||||
else {
|
||||
#ifdef Q_OS_DARWIN
|
||||
// On macOS, $TMPDIR is some random folder under /var/folders/ that nobody can
|
||||
// easily find, so use the system's global tmp directory instead.
|
||||
s_LogDir = "/tmp";
|
||||
#else
|
||||
s_LogDir = QDir::tempPath();
|
||||
#endif
|
||||
s_BoxArtCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart";
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Path
|
||||
{
|
||||
public:
|
||||
static QString getLogDir();
|
||||
|
||||
static QString getBoxArtCacheDir();
|
||||
|
||||
static void initialize(bool portable);
|
||||
|
||||
private:
|
||||
static QString s_LogDir;
|
||||
static QString s_BoxArtCacheDir;
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
#define SER_FPS "fps"
|
||||
#define SER_BITRATE "bitrate"
|
||||
#define SER_FULLSCREEN "fullscreen"
|
||||
#define SER_VSYNC "vsync"
|
||||
#define SER_GAMEOPTS "gameopts"
|
||||
#define SER_HOSTAUDIO "hostaudio"
|
||||
#define SER_MULTICONT "multicontroller"
|
||||
@@ -30,6 +31,7 @@ void StreamingPreferences::reload()
|
||||
fps = settings.value(SER_FPS, 60).toInt();
|
||||
bitrateKbps = settings.value(SER_BITRATE, getDefaultBitrate(width, height, fps)).toInt();
|
||||
fullScreen = settings.value(SER_FULLSCREEN, true).toBool();
|
||||
enableVsync = settings.value(SER_VSYNC, true).toBool();
|
||||
gameOptimizations = settings.value(SER_GAMEOPTS, true).toBool();
|
||||
playAudioOnHost = settings.value(SER_HOSTAUDIO, false).toBool();
|
||||
multiController = settings.value(SER_MULTICONT, true).toBool();
|
||||
@@ -50,6 +52,7 @@ void StreamingPreferences::save()
|
||||
settings.setValue(SER_FPS, fps);
|
||||
settings.setValue(SER_BITRATE, bitrateKbps);
|
||||
settings.setValue(SER_FULLSCREEN, fullScreen);
|
||||
settings.setValue(SER_VSYNC, enableVsync);
|
||||
settings.setValue(SER_GAMEOPTS, gameOptimizations);
|
||||
settings.setValue(SER_HOSTAUDIO, playAudioOnHost);
|
||||
settings.setValue(SER_MULTICONT, multiController);
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
Q_PROPERTY(int fps MEMBER fps NOTIFY displayModeChanged)
|
||||
Q_PROPERTY(int bitrateKbps MEMBER bitrateKbps NOTIFY bitrateChanged)
|
||||
Q_PROPERTY(bool fullScreen MEMBER fullScreen NOTIFY fullScreenChanged)
|
||||
Q_PROPERTY(bool enableVsync MEMBER enableVsync NOTIFY enableVsyncChanged)
|
||||
Q_PROPERTY(bool gameOptimizations MEMBER gameOptimizations NOTIFY gameOptimizationsChanged)
|
||||
Q_PROPERTY(bool playAudioOnHost MEMBER playAudioOnHost NOTIFY playAudioOnHostChanged)
|
||||
Q_PROPERTY(bool multiController MEMBER multiController NOTIFY multiControllerChanged)
|
||||
@@ -68,6 +69,7 @@ public:
|
||||
int fps;
|
||||
int bitrateKbps;
|
||||
bool fullScreen;
|
||||
bool enableVsync;
|
||||
bool gameOptimizations;
|
||||
bool playAudioOnHost;
|
||||
bool multiController;
|
||||
@@ -79,6 +81,7 @@ signals:
|
||||
void displayModeChanged();
|
||||
void bitrateChanged();
|
||||
void fullScreenChanged();
|
||||
void enableVsyncChanged();
|
||||
void gameOptimizationsChanged();
|
||||
void playAudioOnHostChanged();
|
||||
void multiControllerChanged();
|
||||
|
||||
@@ -182,8 +182,13 @@ void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
Uint32 queuedAudio = qMax((int)SDL_GetQueuedAudioSize(s_AudioDevice) - (int)s_BaselinePendingData, 0);
|
||||
Uint32 framesQueued = queuedAudio / (SAMPLES_PER_FRAME * s_ChannelCount * sizeof(short));
|
||||
|
||||
// We must check this prior to the below checks to ensure we don't
|
||||
// underflow if framesQueued - s_PendingHardDrops < 0.
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
}
|
||||
// Pend enough drops to get us back to MIN_QUEUED_FRAMES
|
||||
if (framesQueued - s_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
else if (framesQueued - s_PendingHardDrops > STOP_THE_WORLD_LIMIT) {
|
||||
s_PendingHardDrops = framesQueued - MIN_QUEUED_FRAMES;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Pending hard drop of %u audio frames",
|
||||
@@ -194,10 +199,7 @@ void Session::sdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength)
|
||||
}
|
||||
|
||||
// Determine if this frame should be dropped
|
||||
if (framesQueued <= MIN_QUEUED_FRAMES) {
|
||||
s_PendingDrops = s_PendingHardDrops = 0;
|
||||
}
|
||||
else if (s_PendingHardDrops != 0) {
|
||||
if (s_PendingHardDrops != 0) {
|
||||
// Hard drops happen all at once to forcefully
|
||||
// resync with the source.
|
||||
s_PendingHardDrops--;
|
||||
|
||||
+97
-78
@@ -5,6 +5,12 @@
|
||||
#include <SDL.h>
|
||||
#include "utils.h"
|
||||
|
||||
// HACK: Need to call SetThreadExecutionState() because SDL doesn't
|
||||
#ifdef Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
#include "video/ffmpeg.h"
|
||||
#endif
|
||||
@@ -13,10 +19,8 @@
|
||||
#include "video/sl.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
||||
// Using full-screen desktop allows us to avoid needing to enable V-sync on Windows
|
||||
// and it also avoids some strange flickering issues on my Win7 test machine
|
||||
// with Intel HD 5500 graphics. It also allows Spaces to work on macOS.
|
||||
#if defined(Q_OS_DARWIN)
|
||||
// Using full-screen desktop allows allows Spaces to work on macOS.
|
||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN_DESKTOP
|
||||
#else
|
||||
#define SDL_OS_FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN
|
||||
@@ -106,7 +110,7 @@ void Session::clLogMessage(const char* format, ...)
|
||||
|
||||
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, IVideoDecoder*& chosenDecoder)
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder)
|
||||
{
|
||||
#ifdef HAVE_SLVIDEO
|
||||
chosenDecoder = new SLVideoDecoder();
|
||||
@@ -125,7 +129,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
chosenDecoder = new FFmpegVideoDecoder();
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate)) {
|
||||
if (chosenDecoder->initialize(vds, window, videoFormat, width, height, frameRate, enableVsync)) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"FFmpeg-based video decoder chosen");
|
||||
return true;
|
||||
@@ -212,7 +216,7 @@ bool Session::isHardwareDecodeAvailable(StreamingPreferences::VideoDecoderSelect
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, decoder)) {
|
||||
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, true, decoder)) {
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return false;
|
||||
@@ -471,10 +475,14 @@ void Session::getWindowDimensions(bool fullScreen,
|
||||
{
|
||||
int displayIndex = 0;
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
SDL_assert(displayIndex >= 0);
|
||||
}
|
||||
// If there's a display matching this exact resolution, pick that
|
||||
// one (for native full-screen streaming). Otherwise, assume
|
||||
// display 0 for now. TODO: Default to the screen that the Qt window is on
|
||||
if (fullScreen) {
|
||||
else if (fullScreen) {
|
||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetCurrentDisplayMode(i, &mode) == 0 &&
|
||||
@@ -489,79 +497,44 @@ void Session::getWindowDimensions(bool fullScreen,
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||
if (displayIndex < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetWindowDisplayIndex() failed: %s",
|
||||
SDL_GetError());
|
||||
SDL_Rect usableBounds;
|
||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||
width = usableBounds.w;
|
||||
height = usableBounds.h;
|
||||
x = usableBounds.x;
|
||||
y = usableBounds.y;
|
||||
|
||||
// Assume display 0
|
||||
displayIndex = 0;
|
||||
}
|
||||
}
|
||||
if (m_Window != nullptr) {
|
||||
int top, left, bottom, right;
|
||||
|
||||
if (fullScreen) {
|
||||
SDL_DisplayMode currentMode;
|
||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) < 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get window border size");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_GetCurrentDisplayMode(displayIndex, ¤tMode) == 0) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Using current display mode: %dx%dx%d",
|
||||
currentMode.w, currentMode.h, currentMode.refresh_rate);
|
||||
width = currentMode.w;
|
||||
height = currentMode.h;
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get current display mode: %s",
|
||||
SDL_GetError());
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
}
|
||||
x += left;
|
||||
y += top;
|
||||
|
||||
// Full-screen modes always start at the origin
|
||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
||||
}
|
||||
else {
|
||||
SDL_Rect usableBounds;
|
||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||
width = usableBounds.w;
|
||||
height = usableBounds.h;
|
||||
x = usableBounds.x;
|
||||
y = usableBounds.y;
|
||||
width -= left + right;
|
||||
height -= top + bottom;
|
||||
|
||||
if (m_Window != nullptr) {
|
||||
int top, left, bottom, right;
|
||||
|
||||
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) < 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to get window border size");
|
||||
return;
|
||||
}
|
||||
|
||||
x += left;
|
||||
y += top;
|
||||
|
||||
width -= left + right;
|
||||
height -= top + bottom;
|
||||
|
||||
// If the stream window can fit within the usable drawing area with 1:1
|
||||
// scaling, do that rather than filling the screen.
|
||||
if (m_StreamConfig.width < width && m_StreamConfig.height < height) {
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
}
|
||||
// If the stream window can fit within the usable drawing area with 1:1
|
||||
// scaling, do that rather than filling the screen.
|
||||
if (m_StreamConfig.width < width && m_StreamConfig.height < height) {
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDisplayUsableBounds() failed: %s",
|
||||
SDL_GetError());
|
||||
}
|
||||
else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_GetDisplayUsableBounds() failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
||||
}
|
||||
width = m_StreamConfig.width;
|
||||
height = m_StreamConfig.height;
|
||||
x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,8 +649,8 @@ void Session::exec()
|
||||
if (err != 0) {
|
||||
// We already displayed an error dialog in the stage failure
|
||||
// listener.
|
||||
s_ActiveSessionSemaphore.release();
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
s_ActiveSessionSemaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -694,14 +667,13 @@ void Session::exec()
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
m_Preferences.fullScreen ?
|
||||
SDL_OS_FULLSCREEN_FLAG : 0);
|
||||
0);
|
||||
if (!m_Window) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateWindow() failed: %s",
|
||||
SDL_GetError());
|
||||
s_ActiveSessionSemaphore.release();
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
s_ActiveSessionSemaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -718,6 +690,16 @@ void Session::exec()
|
||||
// creation causes our window to be full screen for some reason
|
||||
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||
}
|
||||
else {
|
||||
// Update the window display mode based on our current monitor
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(m_Window), &mode) == 0) {
|
||||
SDL_SetWindowDisplayMode(m_Window, &mode);
|
||||
}
|
||||
|
||||
// Enter full screen
|
||||
SDL_SetWindowFullscreen(m_Window, SDL_OS_FULLSCREEN_FLAG);
|
||||
}
|
||||
|
||||
QSvgRenderer svgIconRenderer(QString(":/res/moonlight.svg"));
|
||||
QImage svgImage(ICON_SIZE, ICON_SIZE, QImage::Format_RGBA8888);
|
||||
@@ -756,6 +738,17 @@ void Session::exec()
|
||||
// Disable the screen saver
|
||||
SDL_DisableScreenSaver();
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// HACK: SDL doesn't call this, so we must do so to disable the
|
||||
// screensaver when we're in windowed mode. DirectX will disable
|
||||
// it for us when we're in full-screen exclusive mode.
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||
#endif
|
||||
|
||||
// Set timer resolution to 1 ms on Windows for greater
|
||||
// sleep precision and more accurate callback timing.
|
||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "1");
|
||||
|
||||
// Raise the priority of the main thread, since it handles
|
||||
// time-sensitive video rendering
|
||||
if (SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH) < 0) {
|
||||
@@ -812,16 +805,36 @@ void Session::exec()
|
||||
// Fall through
|
||||
case SDL_RENDER_DEVICE_RESET:
|
||||
case SDL_RENDER_TARGETS_RESET:
|
||||
|
||||
SDL_AtomicLock(&m_DecoderLock);
|
||||
|
||||
// Destroy the old decoder
|
||||
delete m_VideoDecoder;
|
||||
|
||||
// Chose a new decoder (hopefully the same one, but possibly
|
||||
// Flush any other pending window events that could
|
||||
// send us back here immediately
|
||||
SDL_PumpEvents();
|
||||
SDL_FlushEvent(SDL_WINDOWEVENT);
|
||||
|
||||
// Update the window display mode based on our current monitor
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetDesktopDisplayMode(SDL_GetWindowDisplayIndex(m_Window), &mode) == 0) {
|
||||
SDL_SetWindowDisplayMode(m_Window, &mode);
|
||||
}
|
||||
|
||||
// Now that the old decoder is dead, flush any events it may
|
||||
// have queued to reset itself (if this reset was the result
|
||||
// of state loss).
|
||||
SDL_PumpEvents();
|
||||
SDL_FlushEvent(SDL_RENDER_DEVICE_RESET);
|
||||
SDL_FlushEvent(SDL_RENDER_TARGETS_RESET);
|
||||
|
||||
// Choose a new decoder (hopefully the same one, but possibly
|
||||
// not if a GPU was removed or something).
|
||||
if (!chooseDecoder(m_Preferences.videoDecoderSelection,
|
||||
m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth,
|
||||
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
|
||||
m_Preferences.enableVsync,
|
||||
s_ActiveSession->m_VideoDecoder)) {
|
||||
SDL_AtomicUnlock(&m_DecoderLock);
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -869,10 +882,16 @@ void Session::exec()
|
||||
SDL_GetError());
|
||||
|
||||
DispatchDeferredCleanup:
|
||||
#ifdef Q_OS_WIN32
|
||||
// HACK: See comment above
|
||||
SetThreadExecutionState(ES_CONTINUOUS);
|
||||
#endif
|
||||
|
||||
// Uncapture the mouse and hide the window immediately,
|
||||
// so we can return to the Qt GUI ASAP.
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
SDL_EnableScreenSaver();
|
||||
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "0");
|
||||
|
||||
// Destroy the decoder, since this must be done on the main thread
|
||||
SDL_AtomicLock(&m_DecoderLock);
|
||||
|
||||
@@ -54,7 +54,7 @@ private:
|
||||
static
|
||||
bool chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window* window, int videoFormat, int width, int height,
|
||||
int frameRate, IVideoDecoder*& chosenDecoder);
|
||||
int frameRate, bool enableVsync, IVideoDecoder*& chosenDecoder);
|
||||
|
||||
static
|
||||
void clStageStarting(int stage);
|
||||
|
||||
@@ -16,7 +16,8 @@ public:
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int frameRate) = 0;
|
||||
int frameRate,
|
||||
bool enableVsync) = 0;
|
||||
virtual bool isHardwareAccelerated() = 0;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||
virtual void renderFrame(SDL_UserEvent* event) = 0;
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
#include "../ffmpeg.h"
|
||||
#include <streaming/streamutils.h>
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <VersionHelpers.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
|
||||
@@ -10,7 +17,6 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x
|
||||
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
||||
|
||||
DXVA2Renderer::DXVA2Renderer() :
|
||||
m_SdlRenderer(nullptr),
|
||||
m_DecService(nullptr),
|
||||
m_Decoder(nullptr),
|
||||
m_SurfacesUsed(0),
|
||||
@@ -23,10 +29,15 @@ DXVA2Renderer::DXVA2Renderer() :
|
||||
{
|
||||
RtlZeroMemory(m_DecSurfaces, sizeof(m_DecSurfaces));
|
||||
RtlZeroMemory(&m_DXVAContext, sizeof(m_DXVAContext));
|
||||
|
||||
// Use MMCSS scheduling for lower scheduling latency while we're streaming
|
||||
DwmEnableMMCSS(TRUE);
|
||||
}
|
||||
|
||||
DXVA2Renderer::~DXVA2Renderer()
|
||||
{
|
||||
DwmEnableMMCSS(FALSE);
|
||||
|
||||
SAFE_COM_RELEASE(m_DecService);
|
||||
SAFE_COM_RELEASE(m_Decoder);
|
||||
SAFE_COM_RELEASE(m_Device);
|
||||
@@ -41,10 +52,6 @@ DXVA2Renderer::~DXVA2Renderer()
|
||||
if (m_Pool != nullptr) {
|
||||
av_buffer_pool_uninit(&m_Pool);
|
||||
}
|
||||
|
||||
if (m_SdlRenderer != nullptr) {
|
||||
SDL_DestroyRenderer(m_SdlRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
void DXVA2Renderer::ffPoolDummyDelete(void*, uint8_t*)
|
||||
@@ -430,34 +437,136 @@ bool DXVA2Renderer::isDecoderBlacklisted()
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height)
|
||||
bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
||||
{
|
||||
SDL_SysWMinfo info;
|
||||
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWindowWMInfo(window, &info);
|
||||
|
||||
IDirect3D9Ex* d3d9ex;
|
||||
HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Direct3DCreate9Ex() failed: %x",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
int adapterIndex = SDL_Direct3D9GetAdapterIndex(SDL_GetWindowDisplayIndex(window));
|
||||
Uint32 windowFlags = SDL_GetWindowFlags(window);
|
||||
|
||||
D3DCAPS9 deviceCaps;
|
||||
d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps);
|
||||
|
||||
D3DDISPLAYMODEEX currentMode;
|
||||
currentMode.Size = sizeof(currentMode);
|
||||
d3d9ex->GetAdapterDisplayModeEx(adapterIndex, ¤tMode, nullptr);
|
||||
|
||||
D3DPRESENT_PARAMETERS d3dpp = {};
|
||||
d3dpp.hDeviceWindow = info.info.win.window;
|
||||
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
|
||||
if ((windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
||||
d3dpp.Windowed = false;
|
||||
d3dpp.BackBufferWidth = currentMode.Width;
|
||||
d3dpp.BackBufferHeight = currentMode.Height;
|
||||
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
||||
d3dpp.BackBufferFormat = currentMode.Format;
|
||||
}
|
||||
else {
|
||||
d3dpp.Windowed = true;
|
||||
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||
|
||||
SDL_GetWindowSize(window, (int*)&d3dpp.BackBufferWidth, (int*)&d3dpp.BackBufferHeight);
|
||||
}
|
||||
|
||||
BOOL dwmEnabled;
|
||||
DwmIsCompositionEnabled(&dwmEnabled);
|
||||
if (d3dpp.Windowed && dwmEnabled) {
|
||||
// If composition enabled, disable v-sync and let DWM manage things
|
||||
// to reduce latency by avoiding double v-syncing.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
|
||||
// D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to
|
||||
// continue while DWM is waiting to render the surface to the display.
|
||||
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
||||
d3dpp.BackBufferCount = 2;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Windowed mode with DWM running");
|
||||
}
|
||||
else if (enableVsync) {
|
||||
// Uncomposited desktop or full-screen exclusive mode with V-sync enabled
|
||||
// We will enable V-sync in this scenario to avoid tearing.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-Sync enabled");
|
||||
}
|
||||
else {
|
||||
// Uncomposited desktop or full-screen exclusive mode with V-sync disabled
|
||||
// We will allowing tearing for lowest latency.
|
||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||
d3dpp.BackBufferCount = 1;
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"V-Sync disabled in tearing mode");
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Windowed: %d | Present Interval: %x",
|
||||
d3dpp.Windowed, d3dpp.PresentationInterval);
|
||||
|
||||
// FFmpeg requires this attribute for doing asynchronous decoding
|
||||
// in a separate thread with this device.
|
||||
int deviceFlags = D3DCREATE_MULTITHREADED;
|
||||
|
||||
if (deviceCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
|
||||
deviceFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
|
||||
}
|
||||
else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No hardware vertex processing support!");
|
||||
deviceFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
|
||||
}
|
||||
|
||||
hr = d3d9ex->CreateDeviceEx(adapterIndex,
|
||||
D3DDEVTYPE_HAL,
|
||||
d3dpp.hDeviceWindow,
|
||||
deviceFlags,
|
||||
&d3dpp,
|
||||
d3dpp.Windowed ? nullptr : ¤tMode,
|
||||
&m_Device);
|
||||
|
||||
d3d9ex->Release();
|
||||
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CreateDeviceEx() failed: %x",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = m_Device->SetMaximumFrameLatency(1);
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SetMaximumFrameLatency() failed: %x",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int, bool enableVsync)
|
||||
{
|
||||
m_VideoFormat = videoFormat;
|
||||
m_VideoWidth = width;
|
||||
m_VideoHeight = height;
|
||||
|
||||
// FFmpeg will be decoding on different threads than the main thread that we're
|
||||
// currently running on right now. We must set this hint so SDL will pass
|
||||
// D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice().
|
||||
SDL_SetHint(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1");
|
||||
|
||||
// We require full-screen desktop mode to avoid having to enable V-sync
|
||||
// to synchronize frame delivery (which has a much higher latency penalty).
|
||||
m_SdlRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (!m_SdlRenderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateRenderer() failed: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Device = SDL_RenderGetD3D9Device(m_SdlRenderer);
|
||||
|
||||
// Draw a black frame until the video stream starts rendering
|
||||
SDL_SetRenderDrawColor(m_SdlRenderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderClear(m_SdlRenderer);
|
||||
SDL_RenderPresent(m_SdlRenderer);
|
||||
|
||||
RtlZeroMemory(&m_Desc, sizeof(m_Desc));
|
||||
|
||||
int alignment;
|
||||
@@ -481,6 +590,10 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2');
|
||||
|
||||
if (!initializeDevice(window, enableVsync)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initializeDecoder()) {
|
||||
return false;
|
||||
}
|
||||
@@ -489,10 +602,24 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
|
||||
return false;
|
||||
}
|
||||
|
||||
// For some reason, using Direct3D9Ex breaks this with multi-monitor setups.
|
||||
// When focus is lost, the window is minimized then immediately restored without
|
||||
// input focus. This glitches out the renderer and a bunch of other stuff.
|
||||
// Direct3D9Ex itself seems to have this minimize on focus loss behavior on its
|
||||
// own, so just disable SDL's handling of the focus loss event.
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
bool DXVA2Renderer::needsTestFrame()
|
||||
{
|
||||
// We validate the DXVA2 profiles are supported
|
||||
// in initialize() so no test frame is required
|
||||
return false;
|
||||
}
|
||||
|
||||
void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame)
|
||||
{
|
||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||
HRESULT hr;
|
||||
@@ -628,33 +755,27 @@ void DXVA2Renderer::renderFrame(AVFrame* frame)
|
||||
|
||||
bltParams.Alpha = DXVA2_Fixed32OpaqueAlpha();
|
||||
|
||||
if (SDL_RenderClear(m_SdlRenderer) != 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_RenderClear() failed: %s",
|
||||
SDL_GetError());
|
||||
av_frame_free(&frame);
|
||||
|
||||
// We're going to cheat a little bit here. It seems SDL's
|
||||
// renderer may flake out in scenarios like moving the window
|
||||
// between monitors, so generate a synthetic reset event for
|
||||
// the main loop to consume.
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
|
||||
return;
|
||||
}
|
||||
m_Device->Clear(0, nullptr, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 0, 0, 0), 0.0f, 0);
|
||||
|
||||
hr = m_Processor->VideoProcessBlt(m_RenderTarget, &bltParams, &sample, 1, nullptr);
|
||||
av_frame_free(&frame);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VideoProcessBlt() failed: %x",
|
||||
hr);
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
return;
|
||||
}
|
||||
|
||||
// We must try to present to trigger SDL's logic to recover the render target,
|
||||
// even if VideoProcessBlt() fails.
|
||||
SDL_RenderPresent(m_SdlRenderer);
|
||||
hr = m_Device->PresentEx(nullptr, nullptr, nullptr, nullptr, 0);
|
||||
if (FAILED(hr)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"PresentEx() failed: %x",
|
||||
hr);
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.h"
|
||||
#include "pacer/pacer.h"
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
@@ -17,13 +18,17 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
|
||||
private:
|
||||
bool initializeDecoder();
|
||||
bool initializeRenderer();
|
||||
bool initializeDevice(SDL_Window* window, bool enableVsync);
|
||||
bool isDecoderBlacklisted();
|
||||
|
||||
static
|
||||
@@ -42,8 +47,6 @@ private:
|
||||
int m_DisplayWidth;
|
||||
int m_DisplayHeight;
|
||||
|
||||
SDL_Renderer* m_SdlRenderer;
|
||||
|
||||
struct dxva_context m_DXVAContext;
|
||||
IDirect3DSurface9* m_DecSurfaces[19];
|
||||
DXVA2_ConfigPictureDecode m_Config;
|
||||
@@ -52,7 +55,7 @@ private:
|
||||
int m_SurfacesUsed;
|
||||
AVBufferPool* m_Pool;
|
||||
|
||||
IDirect3DDevice9* m_Device;
|
||||
IDirect3DDevice9Ex* m_Device;
|
||||
IDirect3DSurface9* m_RenderTarget;
|
||||
IDirectXVideoProcessorService* m_ProcService;
|
||||
IDirectXVideoProcessor* m_Processor;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "displaylinkvsyncsource.h"
|
||||
|
||||
DisplayLinkVsyncSource::DisplayLinkVsyncSource(Pacer* pacer)
|
||||
: m_Pacer(pacer),
|
||||
m_DisplayLink(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DisplayLinkVsyncSource::~DisplayLinkVsyncSource()
|
||||
{
|
||||
if (m_DisplayLink != nullptr) {
|
||||
CVDisplayLinkStop(m_DisplayLink);
|
||||
CVDisplayLinkRelease(m_DisplayLink);
|
||||
}
|
||||
}
|
||||
|
||||
CVReturn
|
||||
DisplayLinkVsyncSource::displayLinkOutputCallback(
|
||||
CVDisplayLinkRef,
|
||||
const CVTimeStamp* /* now */,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext)
|
||||
{
|
||||
auto me = reinterpret_cast<DisplayLinkVsyncSource*>(displayLinkContext);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool DisplayLinkVsyncSource::initialize(SDL_Window*, int displayFps)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
CVDisplayLinkStart(m_DisplayLink);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
class DisplayLinkVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
DisplayLinkVsyncSource(Pacer* pacer);
|
||||
|
||||
virtual ~DisplayLinkVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
|
||||
private:
|
||||
static
|
||||
CVReturn
|
||||
displayLinkOutputCallback(
|
||||
CVDisplayLinkRef,
|
||||
const CVTimeStamp* now,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
int m_DisplayFps;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "dxvsyncsource.h"
|
||||
|
||||
// Useful references:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=467617
|
||||
// https://chromium.googlesource.com/chromium/src.git/+/c564f2fe339b2b2abb0c8773c90c83215670ea71/gpu/ipc/service/gpu_vsync_provider_win.cc
|
||||
|
||||
DxVsyncSource::DxVsyncSource(Pacer* pacer) :
|
||||
m_Pacer(pacer),
|
||||
m_Thread(nullptr),
|
||||
m_Gdi32Handle(nullptr)
|
||||
{
|
||||
SDL_AtomicSet(&m_Stopping, 0);
|
||||
}
|
||||
|
||||
DxVsyncSource::~DxVsyncSource()
|
||||
{
|
||||
if (m_Thread != nullptr) {
|
||||
SDL_AtomicSet(&m_Stopping, 1);
|
||||
SDL_WaitThread(m_Thread, nullptr);
|
||||
}
|
||||
|
||||
if (m_Gdi32Handle != nullptr) {
|
||||
FreeLibrary(m_Gdi32Handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool DxVsyncSource::initialize(SDL_Window* window, int displayFps)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
|
||||
m_Gdi32Handle = LoadLibraryA("gdi32.dll");
|
||||
if (m_Gdi32Handle == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to load gdi32.dll: %d",
|
||||
GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_D3DKMTOpenAdapterFromHdc = (PFND3DKMTOPENADAPTERFROMHDC)GetProcAddress(m_Gdi32Handle, "D3DKMTOpenAdapterFromHdc");
|
||||
m_D3DKMTCloseAdapter = (PFND3DKMTCLOSEADAPTER)GetProcAddress(m_Gdi32Handle, "D3DKMTCloseAdapter");
|
||||
m_D3DKMTWaitForVerticalBlankEvent = (PFND3DKMTWAITFORVERTICALBLANKEVENT)GetProcAddress(m_Gdi32Handle, "D3DKMTWaitForVerticalBlankEvent");
|
||||
|
||||
if (m_D3DKMTOpenAdapterFromHdc == nullptr ||
|
||||
m_D3DKMTCloseAdapter == nullptr ||
|
||||
m_D3DKMTWaitForVerticalBlankEvent == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Missing required function in gdi32.dll");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
m_Window = info.info.win.window;
|
||||
|
||||
m_Thread = SDL_CreateThread(vsyncThread, "DX Vsync Thread", this);
|
||||
if (m_Thread == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to create DX V-sync thread: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DxVsyncSource::vsyncThread(void* context)
|
||||
{
|
||||
DxVsyncSource* me = reinterpret_cast<DxVsyncSource*>(context);
|
||||
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
|
||||
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
|
||||
HMONITOR lastMonitor = nullptr;
|
||||
DEVMODEA monitorMode;
|
||||
monitorMode.dmSize = sizeof(monitorMode);
|
||||
|
||||
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
||||
D3DKMT_WAITFORVERTICALBLANKEVENT waitForVblankEventParams;
|
||||
NTSTATUS status;
|
||||
|
||||
// If the monitor has changed from last time, open the new adapter
|
||||
HMONITOR currentMonitor = MonitorFromWindow(me->m_Window, MONITOR_DEFAULTTONEAREST);
|
||||
if (currentMonitor != lastMonitor) {
|
||||
MONITORINFOEXA monitorInfo = {};
|
||||
monitorInfo.cbSize = sizeof(monitorInfo);
|
||||
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"GetMonitorInfo() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"EnumDisplaySettings() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Monitor changed: %s %d Hz",
|
||||
monitorInfo.szDevice,
|
||||
monitorMode.dmDisplayFrequency);
|
||||
|
||||
if (openAdapterParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
|
||||
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
openAdapterParams.hDc = CreateDCA(nullptr, monitorInfo.szDevice, nullptr, nullptr);
|
||||
if (!openAdapterParams.hDc) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CreateDC() failed: %d",
|
||||
GetLastError());
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
status = me->m_D3DKMTOpenAdapterFromHdc(&openAdapterParams);
|
||||
DeleteDC(openAdapterParams.hDc);
|
||||
|
||||
if (status != STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"D3DKMTOpenAdapterFromHdc() failed: %x",
|
||||
status);
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastMonitor = currentMonitor;
|
||||
}
|
||||
|
||||
waitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
|
||||
waitForVblankEventParams.hDevice = 0;
|
||||
waitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
|
||||
|
||||
status = me->m_D3DKMTWaitForVerticalBlankEvent(&waitForVblankEventParams);
|
||||
if (status != STATUS_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"D3DKMTWaitForVerticalBlankEvent() failed: %x",
|
||||
status);
|
||||
SDL_Delay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
||||
}
|
||||
|
||||
if (openAdapterParams.hAdapter != 0) {
|
||||
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
|
||||
closeAdapterParams.hAdapter = openAdapterParams.hAdapter;
|
||||
me->m_D3DKMTCloseAdapter(&closeAdapterParams);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
// from <D3dkmthk.h>
|
||||
typedef LONG NTSTATUS;
|
||||
typedef UINT D3DKMT_HANDLE;
|
||||
typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
|
||||
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
||||
#define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L)
|
||||
typedef struct _D3DKMT_OPENADAPTERFROMHDC {
|
||||
HDC hDc;
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
LUID AdapterLuid;
|
||||
D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
|
||||
} D3DKMT_OPENADAPTERFROMHDC;
|
||||
typedef struct _D3DKMT_CLOSEADAPTER {
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
} D3DKMT_CLOSEADAPTER;
|
||||
typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT {
|
||||
D3DKMT_HANDLE hAdapter;
|
||||
D3DKMT_HANDLE hDevice;
|
||||
D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
|
||||
} D3DKMT_WAITFORVERTICALBLANKEVENT;
|
||||
typedef NTSTATUS(APIENTRY* PFND3DKMTOPENADAPTERFROMHDC)(D3DKMT_OPENADAPTERFROMHDC*);
|
||||
typedef NTSTATUS(APIENTRY* PFND3DKMTCLOSEADAPTER)(D3DKMT_CLOSEADAPTER*);
|
||||
typedef NTSTATUS(APIENTRY* PFND3DKMTWAITFORVERTICALBLANKEVENT)(D3DKMT_WAITFORVERTICALBLANKEVENT*);
|
||||
|
||||
class DxVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
DxVsyncSource(Pacer* pacer);
|
||||
|
||||
virtual ~DxVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
|
||||
private:
|
||||
static int vsyncThread(void* context);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
SDL_Thread* m_Thread;
|
||||
SDL_atomic_t m_Stopping;
|
||||
HMODULE m_Gdi32Handle;
|
||||
HWND m_Window;
|
||||
int m_DisplayFps;
|
||||
|
||||
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
|
||||
PFND3DKMTCLOSEADAPTER m_D3DKMTCloseAdapter;
|
||||
PFND3DKMTWAITFORVERTICALBLANKEVENT m_D3DKMTWaitForVerticalBlankEvent;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "nullthreadedvsyncsource.h"
|
||||
|
||||
NullThreadedVsyncSource::NullThreadedVsyncSource(Pacer* pacer) :
|
||||
m_Pacer(pacer),
|
||||
m_Thread(nullptr)
|
||||
{
|
||||
SDL_AtomicSet(&m_Stopping, 0);
|
||||
}
|
||||
|
||||
NullThreadedVsyncSource::~NullThreadedVsyncSource()
|
||||
{
|
||||
if (m_Thread != nullptr) {
|
||||
SDL_AtomicSet(&m_Stopping, 1);
|
||||
SDL_WaitThread(m_Thread, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool NullThreadedVsyncSource::initialize(SDL_Window*, int displayFps)
|
||||
{
|
||||
m_DisplayFps = displayFps;
|
||||
m_Thread = SDL_CreateThread(vsyncThread, "Null Vsync Thread", this);
|
||||
if (m_Thread == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Unable to create DX V-sync thread: %s",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int NullThreadedVsyncSource::vsyncThread(void* context)
|
||||
{
|
||||
NullThreadedVsyncSource* me = reinterpret_cast<NullThreadedVsyncSource*>(context);
|
||||
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||
|
||||
while (SDL_AtomicGet(&me->m_Stopping) == 0) {
|
||||
me->m_Pacer->vsyncCallback(1000 / me->m_DisplayFps);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "pacer.h"
|
||||
|
||||
class NullThreadedVsyncSource : public IVsyncSource
|
||||
{
|
||||
public:
|
||||
NullThreadedVsyncSource(Pacer* pacer);
|
||||
|
||||
virtual ~NullThreadedVsyncSource();
|
||||
|
||||
virtual bool initialize(SDL_Window* window, int displayFps);
|
||||
|
||||
private:
|
||||
static int vsyncThread(void* context);
|
||||
|
||||
Pacer* m_Pacer;
|
||||
SDL_Thread* m_Thread;
|
||||
SDL_atomic_t m_Stopping;
|
||||
int m_DisplayFps;
|
||||
};
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "pacer.h"
|
||||
|
||||
#include "nullthreadedvsyncsource.h"
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "displaylinkvsyncsource.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "dxvsyncsource.h"
|
||||
#endif
|
||||
|
||||
#define FRAME_HISTORY_ENTRIES 8
|
||||
|
||||
// We may be woken up slightly late so don't go all the way
|
||||
// up to the next V-sync since we may accidentally step into
|
||||
// the next V-sync period. It also takes some amount of time
|
||||
// to do the render itself, so we can't render right before
|
||||
// V-sync happens.
|
||||
#define TIMER_SLACK_MS 3
|
||||
|
||||
Pacer::Pacer(IFFmpegRenderer* renderer) :
|
||||
m_FrameQueueLock(0),
|
||||
m_VsyncSource(nullptr),
|
||||
m_VsyncRenderer(renderer),
|
||||
m_MaxVideoFps(0),
|
||||
m_DisplayFps(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Pacer::~Pacer()
|
||||
{
|
||||
// Stop V-sync callbacks
|
||||
delete m_VsyncSource;
|
||||
m_VsyncSource = nullptr;
|
||||
|
||||
while (!m_FrameQueue.isEmpty()) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
||||
// or an event synchronized with V-sync
|
||||
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
||||
{
|
||||
// Make sure initialize() has been called
|
||||
SDL_assert(m_MaxVideoFps != 0);
|
||||
|
||||
SDL_assert(timeUntilNextVsyncMillis >= TIMER_SLACK_MS);
|
||||
|
||||
Uint32 vsyncCallbackStartTime = SDL_GetTicks();
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
|
||||
// If the queue length history entries are large, be strict
|
||||
// about dropping excess frames.
|
||||
int frameDropTarget = 1;
|
||||
|
||||
// If we may get more frames per second than we can display, use
|
||||
// frame history to drop frames only if consistently above the
|
||||
// one queued frame mark.
|
||||
if (m_MaxVideoFps >= m_DisplayFps) {
|
||||
for (int i = 0; i < m_FrameQueueHistory.count(); i++) {
|
||||
if (m_FrameQueueHistory[i] <= 1) {
|
||||
// Be lenient as long as the queue length
|
||||
// resolves before the end of frame history
|
||||
frameDropTarget = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) {
|
||||
m_FrameQueueHistory.dequeue();
|
||||
}
|
||||
|
||||
m_FrameQueueHistory.enqueue(m_FrameQueue.count());
|
||||
}
|
||||
|
||||
// Catch up if we're several frames ahead
|
||||
while (m_FrameQueue.count() > frameDropTarget) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
|
||||
if (m_FrameQueue.isEmpty()) {
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
while (!SDL_TICKS_PASSED(SDL_GetTicks(),
|
||||
vsyncCallbackStartTime + timeUntilNextVsyncMillis - TIMER_SLACK_MS)) {
|
||||
SDL_Delay(1);
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
if (!m_FrameQueue.isEmpty()) {
|
||||
// Don't release the lock
|
||||
goto RenderNextFrame;
|
||||
}
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
}
|
||||
|
||||
// Nothing to render at this time
|
||||
return;
|
||||
}
|
||||
|
||||
RenderNextFrame:
|
||||
// Grab the first frame
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
// Render it
|
||||
m_VsyncRenderer->renderFrameAtVsync(frame);
|
||||
|
||||
// Free the frame
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enableVsync)
|
||||
{
|
||||
m_MaxVideoFps = maxVideoFps;
|
||||
m_EnableVsync = enableVsync;
|
||||
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(window);
|
||||
if (displayIndex < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Failed to get current display: %s",
|
||||
SDL_GetError());
|
||||
|
||||
// Assume display 0 if it fails
|
||||
displayIndex = 0;
|
||||
}
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
if (SDL_GetCurrentDisplayMode(displayIndex, &mode) == 0) {
|
||||
// May be zero if undefined
|
||||
m_DisplayFps = mode.refresh_rate;
|
||||
}
|
||||
|
||||
if (m_EnableVsync) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Frame pacing in tear-free mode: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
m_VsyncSource = new DisplayLinkVsyncSource(this);
|
||||
#elif defined(Q_OS_WIN32)
|
||||
m_VsyncSource = new DxVsyncSource(this);
|
||||
#else
|
||||
// Platforms without a VsyncSource will just render frames
|
||||
// immediately like they used to.
|
||||
#endif
|
||||
|
||||
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Minimal latency tearing mode: target %d Hz with %d FPS stream",
|
||||
m_DisplayFps, m_MaxVideoFps);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Pacer::submitFrame(AVFrame* frame)
|
||||
{
|
||||
// Make sure initialize() has been called
|
||||
SDL_assert(m_MaxVideoFps != 0);
|
||||
|
||||
// Queue the frame until the V-sync callback if
|
||||
// we have a V-sync source, otherwise deliver it
|
||||
// immediately and hope for the best.
|
||||
if (isUsingFrameQueue()) {
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
m_FrameQueue.enqueue(frame);
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
}
|
||||
else {
|
||||
m_VsyncRenderer->renderFrameAtVsync(frame);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool Pacer::isUsingFrameQueue()
|
||||
{
|
||||
return m_VsyncSource != nullptr;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "../renderer.h"
|
||||
|
||||
#include <QQueue>
|
||||
|
||||
class IVsyncSource {
|
||||
public:
|
||||
virtual ~IVsyncSource() {}
|
||||
virtual bool initialize(SDL_Window* window, int displayFps) = 0;
|
||||
};
|
||||
|
||||
class Pacer
|
||||
{
|
||||
public:
|
||||
Pacer(IFFmpegRenderer* renderer);
|
||||
|
||||
~Pacer();
|
||||
|
||||
void submitFrame(AVFrame* frame);
|
||||
|
||||
bool initialize(SDL_Window* window, int maxVideoFps, bool enableVsync);
|
||||
|
||||
void vsyncCallback(int timeUntilNextVsyncMillis);
|
||||
|
||||
bool isUsingFrameQueue();
|
||||
|
||||
private:
|
||||
QQueue<AVFrame*> m_FrameQueue;
|
||||
QQueue<int> m_FrameQueueHistory;
|
||||
SDL_SpinLock m_FrameQueueLock;
|
||||
|
||||
IVsyncSource* m_VsyncSource;
|
||||
IFFmpegRenderer* m_VsyncRenderer;
|
||||
int m_MaxVideoFps;
|
||||
int m_DisplayFps;
|
||||
bool m_EnableVsync;
|
||||
};
|
||||
@@ -12,9 +12,12 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height) = 0;
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync) = 0;
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context) = 0;
|
||||
virtual void renderFrame(AVFrame* frame) = 0;
|
||||
virtual void renderFrameAtVsync(AVFrame* frame) = 0;
|
||||
virtual bool needsTestFrame() = 0;
|
||||
};
|
||||
|
||||
class SdlRenderer : public IFFmpegRenderer {
|
||||
@@ -24,9 +27,12 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
|
||||
private:
|
||||
SDL_Renderer* m_Renderer;
|
||||
|
||||
@@ -30,12 +30,31 @@ bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SdlRenderer::needsTestFrame()
|
||||
{
|
||||
// This renderer should always work
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SdlRenderer::initialize(SDL_Window* window,
|
||||
int,
|
||||
int width,
|
||||
int height)
|
||||
int height,
|
||||
int,
|
||||
bool enableVsync)
|
||||
{
|
||||
m_Renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
|
||||
if ((SDL_GetWindowFlags(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
|
||||
// have compositors that make rendering tear-free. Linux compositor varies by distro and user
|
||||
// configuration but doesn't seem feasible to detect here.
|
||||
if (enableVsync) {
|
||||
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
|
||||
}
|
||||
}
|
||||
|
||||
m_Renderer = SDL_CreateRenderer(window, -1, rendererFlags);
|
||||
if (!m_Renderer) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"SDL_CreateRenderer() failed: %s",
|
||||
@@ -67,7 +86,7 @@ bool SdlRenderer::initialize(SDL_Window* window,
|
||||
return true;
|
||||
}
|
||||
|
||||
void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
void SdlRenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
SDL_UpdateYUVTexture(m_Texture, nullptr,
|
||||
frame->data[0],
|
||||
@@ -77,9 +96,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
|
||||
frame->data[2],
|
||||
frame->linesize[2]);
|
||||
|
||||
// Done with the frame now
|
||||
av_frame_free(&frame);
|
||||
|
||||
SDL_RenderClear(m_Renderer);
|
||||
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(m_Renderer);
|
||||
|
||||
@@ -27,7 +27,7 @@ VAAPIRenderer::~VAAPIRenderer()
|
||||
}
|
||||
|
||||
bool
|
||||
VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height)
|
||||
VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height, int, bool)
|
||||
{
|
||||
int err;
|
||||
SDL_SysWMinfo info;
|
||||
@@ -141,8 +141,16 @@ VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
VAAPIRenderer::needsTestFrame()
|
||||
{
|
||||
// We need a test frame to see if this VAAPI driver
|
||||
// supports the profile used for streaming
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||
VAAPIRenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
VASurfaceID surface = (VASurfaceID)(uintptr_t)frame->data[3];
|
||||
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
|
||||
@@ -197,6 +205,4 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
|
||||
// We don't accept anything else in initialize().
|
||||
SDL_assert(false);
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,12 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
|
||||
private:
|
||||
int m_WindowSystem;
|
||||
|
||||
@@ -54,7 +54,7 @@ VDPAURenderer::~VDPAURenderer()
|
||||
}
|
||||
}
|
||||
|
||||
bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height)
|
||||
bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height, int, bool)
|
||||
{
|
||||
int err;
|
||||
VdpStatus status;
|
||||
@@ -223,7 +223,14 @@ bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context)
|
||||
return true;
|
||||
}
|
||||
|
||||
void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||
bool VDPAURenderer::needsTestFrame()
|
||||
{
|
||||
// We need a test frame to see if this VDPAU driver
|
||||
// supports the profile used for streaming
|
||||
return true;
|
||||
}
|
||||
|
||||
void VDPAURenderer::renderFrameAtVsync(AVFrame* frame)
|
||||
{
|
||||
VdpStatus status;
|
||||
VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3];
|
||||
@@ -235,14 +242,14 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||
// We need to create the mixer on the fly, because we don't know the dimensions
|
||||
// of our video surfaces in advance of decoding
|
||||
if (m_VideoMixer == 0) {
|
||||
VdpChromaType chroma;
|
||||
VdpChromaType videoSurfaceChroma;
|
||||
uint32_t videoSurfaceWidth, videoSurfaceHeight;
|
||||
status = m_VdpVideoSurfaceGetParameters(videoSurface, &chroma, &videoSurfaceWidth, &videoSurfaceHeight);
|
||||
status = m_VdpVideoSurfaceGetParameters(videoSurface, &videoSurfaceChroma,
|
||||
&videoSurfaceWidth, &videoSurfaceHeight);
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpVideoSurfaceGetParameters() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
av_frame_free(&frame);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -250,14 +257,16 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||
"VDPAU surface size: %dx%d",
|
||||
videoSurfaceWidth, videoSurfaceHeight);
|
||||
|
||||
#define PARAM_COUNT 2
|
||||
#define PARAM_COUNT 3
|
||||
const VdpVideoMixerParameter params[PARAM_COUNT] = {
|
||||
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
|
||||
VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
|
||||
VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
|
||||
};
|
||||
const void* const paramValues[PARAM_COUNT] = {
|
||||
&videoSurfaceWidth,
|
||||
&videoSurfaceHeight,
|
||||
&videoSurfaceChroma,
|
||||
};
|
||||
|
||||
status = m_VdpVideoMixerCreate(m_Device, 0, nullptr,
|
||||
@@ -267,7 +276,6 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpVideoMixerCreate() failed: %s",
|
||||
m_VdpGetErrorString(status));
|
||||
av_frame_free(&frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -310,10 +318,6 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
|
||||
nullptr,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
// The decoder can have this surface back now
|
||||
av_frame_free(&frame);
|
||||
|
||||
if (status != VDP_STATUS_OK) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"VdpVideoMixerRender() failed: %s",
|
||||
|
||||
@@ -16,9 +16,12 @@ public:
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height);
|
||||
int height,
|
||||
int maxFps,
|
||||
bool enableVsync);
|
||||
virtual bool prepareDecoderContext(AVCodecContext* context);
|
||||
virtual void renderFrame(AVFrame* frame);
|
||||
virtual void renderFrameAtVsync(AVFrame* frame);
|
||||
virtual bool needsTestFrame();
|
||||
|
||||
private:
|
||||
uint32_t m_VideoWidth, m_VideoHeight;
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
// libavutil both defining AVMediaType
|
||||
#define AVMediaType AVMediaType_FFmpeg
|
||||
#include "vt.h"
|
||||
#include "pacer/pacer.h"
|
||||
#undef AVMediaType
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
#include <Limelight.h>
|
||||
|
||||
#include <QQueue>
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <VideoToolbox/VideoToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
|
||||
#define FRAME_HISTORY_ENTRIES 8
|
||||
|
||||
class VTRenderer : public IFFmpegRenderer
|
||||
{
|
||||
@@ -23,9 +20,7 @@ public:
|
||||
: m_HwContext(nullptr),
|
||||
m_DisplayLayer(nullptr),
|
||||
m_FormatDesc(nullptr),
|
||||
m_View(nullptr),
|
||||
m_DisplayLink(nullptr),
|
||||
m_FrameQueueLock(0)
|
||||
m_View(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,63 +34,28 @@ public:
|
||||
CFRelease(m_FormatDesc);
|
||||
}
|
||||
|
||||
if (m_DisplayLink != nullptr) {
|
||||
CVDisplayLinkStop(m_DisplayLink);
|
||||
CVDisplayLinkRelease(m_DisplayLink);
|
||||
}
|
||||
|
||||
while (!m_FrameQueue.isEmpty()) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
if (m_View != nullptr) {
|
||||
[m_View removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
void drawFrame(uint64_t vsyncTime)
|
||||
// Caller frees frame after we return
|
||||
virtual void renderFrameAtVsync(AVFrame* frame) override
|
||||
{
|
||||
OSStatus status;
|
||||
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting failed AVSampleBufferDisplay layer");
|
||||
|
||||
int frameDropTarget;
|
||||
|
||||
// If the queue length history entries are large, be strict
|
||||
// about dropping excess frames.
|
||||
frameDropTarget = 1;
|
||||
for (int i = 0; i < m_FrameQueueHistory.count(); i++) {
|
||||
if (m_FrameQueueHistory[i] <= 1) {
|
||||
// Be lenient as long as the queue length
|
||||
// resolves before the end of frame history
|
||||
frameDropTarget = 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) {
|
||||
m_FrameQueueHistory.dequeue();
|
||||
}
|
||||
|
||||
m_FrameQueueHistory.enqueue(m_FrameQueue.count());
|
||||
|
||||
// Catch up if we're several frames ahead
|
||||
while (m_FrameQueue.count() > frameDropTarget) {
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
if (m_FrameQueue.isEmpty()) {
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
// Trigger the main thread to recreate the decoder
|
||||
SDL_Event event;
|
||||
event.type = SDL_RENDER_TARGETS_RESET;
|
||||
SDL_PushEvent(&event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the first frame
|
||||
AVFrame* frame = m_FrameQueue.dequeue();
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
|
||||
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
|
||||
|
||||
// If the format has changed or doesn't exist yet, construct it with the
|
||||
// pixel buffer data
|
||||
if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) {
|
||||
@@ -108,7 +68,6 @@ public:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CMVideoFormatDescriptionCreateForImageBuffer() failed: %d",
|
||||
status);
|
||||
av_frame_free(&frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -117,7 +76,7 @@ public:
|
||||
CMSampleTimingInfo timingInfo = {
|
||||
.duration = kCMTimeInvalid,
|
||||
.decodeTimeStamp = kCMTimeInvalid,
|
||||
.presentationTimeStamp = CMTimeMake(vsyncTime, 1000 * 1000 * 1000)
|
||||
.presentationTimeStamp = CMTimeMake(mach_absolute_time(), 1000 * 1000 * 1000)
|
||||
};
|
||||
|
||||
CMSampleBufferRef sampleBuffer;
|
||||
@@ -130,45 +89,20 @@ public:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"CMSampleBufferCreateReadyWithImageBuffer() failed: %d",
|
||||
status);
|
||||
av_frame_free(&frame);
|
||||
return;
|
||||
}
|
||||
|
||||
[m_DisplayLayer enqueueSampleBuffer:sampleBuffer];
|
||||
|
||||
CFRelease(sampleBuffer);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
static
|
||||
CVReturn
|
||||
displayLinkOutputCallback(
|
||||
CVDisplayLinkRef,
|
||||
const CVTimeStamp* now,
|
||||
const CVTimeStamp* /* vsyncTime */,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void *displayLinkContext)
|
||||
{
|
||||
VTRenderer* me = reinterpret_cast<VTRenderer*>(displayLinkContext);
|
||||
|
||||
// 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->drawFrame(now->hostTime);
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
virtual bool initialize(SDL_Window* window,
|
||||
int videoFormat,
|
||||
int,
|
||||
int) override
|
||||
int,
|
||||
int,
|
||||
bool) override
|
||||
{
|
||||
int err;
|
||||
|
||||
@@ -243,10 +177,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&m_DisplayLink);
|
||||
CVDisplayLinkSetOutputCallback(m_DisplayLink, displayLinkOutputCallback, this);
|
||||
CVDisplayLinkStart(m_DisplayLink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -260,17 +190,10 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void renderFrame(AVFrame* frame) override
|
||||
virtual bool needsTestFrame() override
|
||||
{
|
||||
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Resetting failed AVSampleBufferDisplay layer");
|
||||
setupDisplayLayer();
|
||||
}
|
||||
|
||||
SDL_AtomicLock(&m_FrameQueueLock);
|
||||
m_FrameQueue.enqueue(frame);
|
||||
SDL_AtomicUnlock(&m_FrameQueueLock);
|
||||
// We query VT to determine whether the codec is supported
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -296,10 +219,6 @@ private:
|
||||
AVSampleBufferDisplayLayer* m_DisplayLayer;
|
||||
CMVideoFormatDescriptionRef m_FormatDesc;
|
||||
NSView* m_View;
|
||||
CVDisplayLinkRef m_DisplayLink;
|
||||
QQueue<AVFrame*> m_FrameQueue;
|
||||
QQueue<int> m_FrameQueueHistory;
|
||||
SDL_SpinLock m_FrameQueueLock;
|
||||
};
|
||||
|
||||
IFFmpegRenderer* VTRendererFactory::createRenderer() {
|
||||
|
||||
@@ -53,7 +53,8 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
|
||||
m_DecodeBuffer(1024 * 1024, 0),
|
||||
m_HwDecodeCfg(nullptr),
|
||||
m_Renderer(nullptr),
|
||||
m_ConsecutiveFailedDecodes(0)
|
||||
m_ConsecutiveFailedDecodes(0),
|
||||
m_Pacer(nullptr)
|
||||
{
|
||||
av_init_packet(&m_Pkt);
|
||||
SDL_AtomicSet(&m_QueuedFrames, 0);
|
||||
@@ -91,19 +92,29 @@ void FFmpegVideoDecoder::reset()
|
||||
dropFrame(&event.user);
|
||||
}
|
||||
else {
|
||||
SDL_Delay(100);
|
||||
SDL_Delay(10);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
}
|
||||
|
||||
avcodec_free_context(&m_VideoDecoderCtx);
|
||||
delete m_Pacer;
|
||||
m_Pacer = nullptr;
|
||||
|
||||
delete m_Renderer;
|
||||
m_Renderer = nullptr;
|
||||
|
||||
avcodec_free_context(&m_VideoDecoderCtx);
|
||||
}
|
||||
|
||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, int videoFormat, int width, int height, bool testOnly)
|
||||
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly)
|
||||
{
|
||||
m_Pacer = new Pacer(m_Renderer);
|
||||
if (!m_Pacer->initialize(window, maxFps, enableVsync)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_VideoDecoderCtx = avcodec_alloc_context3(decoder);
|
||||
if (!m_VideoDecoderCtx) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -218,7 +229,8 @@ bool FFmpegVideoDecoder::initialize(
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int)
|
||||
int maxFps,
|
||||
bool enableVsync)
|
||||
{
|
||||
AVCodec* decoder;
|
||||
|
||||
@@ -253,8 +265,8 @@ bool FFmpegVideoDecoder::initialize(
|
||||
m_HwDecodeCfg = nullptr;
|
||||
m_Renderer = new SdlRenderer();
|
||||
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height) &&
|
||||
completeInitialization(decoder, videoFormat, width, height, false)) {
|
||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@@ -269,20 +281,26 @@ bool FFmpegVideoDecoder::initialize(
|
||||
}
|
||||
|
||||
m_HwDecodeCfg = config;
|
||||
// Submit test frame to ensure this codec really works
|
||||
if (m_Renderer->initialize(window, videoFormat, width, height) &&
|
||||
completeInitialization(decoder, videoFormat, width, height, true)) {
|
||||
// OK, it worked, so now let's initialize it for real
|
||||
reset();
|
||||
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height) &&
|
||||
completeInitialization(decoder, videoFormat, width, height, false)) {
|
||||
return true;
|
||||
// Initialize the hardware codec and submit a test frame if the renderer needs it
|
||||
if (m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, m_Renderer->needsTestFrame())) {
|
||||
if (m_Renderer->needsTestFrame()) {
|
||||
// The test worked, so now let's initialize it for real
|
||||
reset();
|
||||
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
|
||||
m_Renderer->initialize(window, videoFormat, width, height, maxFps, enableVsync) &&
|
||||
completeInitialization(decoder, window, videoFormat, width, height, maxFps, enableVsync, false)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Decoder failed to initialize after successful test");
|
||||
reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Decoder failed to initialize after successful test");
|
||||
reset();
|
||||
// No test required. Good to go now.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -380,7 +398,7 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
|
||||
void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
||||
{
|
||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||
m_Renderer->renderFrame(frame);
|
||||
m_Pacer->submitFrame(frame);
|
||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
||||
}
|
||||
|
||||
@@ -388,6 +406,15 @@ void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
|
||||
void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event)
|
||||
{
|
||||
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
|
||||
av_frame_free(&frame);
|
||||
// If Pacer is using a frame queue, we can just queue the
|
||||
// frame and let it decide whether to drop or not. If Pacer
|
||||
// is submitting directly for rendering, we drop the frame
|
||||
// ourselves.
|
||||
if (m_Pacer->isUsingFrameQueue()) {
|
||||
m_Pacer->submitFrame(frame);
|
||||
}
|
||||
else {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
SDL_AtomicDecRef(&m_QueuedFrames);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "decoder.h"
|
||||
#include "ffmpeg-renderers/renderer.h"
|
||||
#include "ffmpeg-renderers/pacer/pacer.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
@@ -16,7 +17,8 @@ public:
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int frameRate) override;
|
||||
int maxFps,
|
||||
bool enableVsync) override;
|
||||
virtual bool isHardwareAccelerated() override;
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du) override;
|
||||
virtual void renderFrame(SDL_UserEvent* event) override;
|
||||
@@ -25,8 +27,9 @@ public:
|
||||
virtual IFFmpegRenderer* getRenderer();
|
||||
|
||||
private:
|
||||
bool completeInitialization(AVCodec* decoder, int videoFormat,
|
||||
int width, int height, bool testOnly);
|
||||
bool completeInitialization(AVCodec* decoder, SDL_Window* window,
|
||||
int videoFormat, int width, int height,
|
||||
int maxFps, bool enableVsync, bool testOnly);
|
||||
|
||||
IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg);
|
||||
|
||||
@@ -43,6 +46,7 @@ private:
|
||||
IFFmpegRenderer* m_Renderer;
|
||||
SDL_atomic_t m_QueuedFrames;
|
||||
int m_ConsecutiveFailedDecodes;
|
||||
Pacer* m_Pacer;
|
||||
|
||||
static const uint8_t k_H264TestFrame[];
|
||||
static const uint8_t k_HEVCTestFrame[];
|
||||
|
||||
@@ -28,7 +28,7 @@ SLVideoDecoder::isHardwareAccelerated()
|
||||
bool
|
||||
SLVideoDecoder::initialize(StreamingPreferences::VideoDecoderSelection vds,
|
||||
SDL_Window*,
|
||||
int videoFormat, int, int, int frameRate)
|
||||
int videoFormat, int, int, int frameRate, bool)
|
||||
{
|
||||
// SLVideo only supports hardware decoding
|
||||
if (vds == StreamingPreferences::VDS_FORCE_SOFTWARE) {
|
||||
|
||||
@@ -14,7 +14,8 @@ public:
|
||||
int videoFormat,
|
||||
int width,
|
||||
int height,
|
||||
int frameRate);
|
||||
int frameRate,
|
||||
bool enableVsync);
|
||||
virtual bool isHardwareAccelerated();
|
||||
virtual int submitDecodeUnit(PDECODE_UNIT du);
|
||||
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ SUBDIRS = \
|
||||
qmdnsengine \
|
||||
app
|
||||
|
||||
CONFIG += ordered
|
||||
# Build the dependencies in parallel before the final app
|
||||
app.depends = qmdnsengine moonlight-common-c
|
||||
|
||||
# Support debug and release builds from command line for CI
|
||||
CONFIG += debug_and_release
|
||||
|
||||
@@ -88,21 +88,24 @@ if "%SIGN%"=="1" (
|
||||
)
|
||||
)
|
||||
|
||||
echo Building installer
|
||||
set VCREDIST_INSTALLER=%VCREDIST_PATH%\vcredist_%ARCH%.exe
|
||||
msbuild %SOURCE_ROOT%\wix\Moonlight.sln /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
echo Building portable package
|
||||
rem This must be done after WiX harvesting and signing, since the VCRT dlls are MS signed
|
||||
rem and should not be harvested for inclusion in the full installer
|
||||
copy "%VCREDIST_PATH%\%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %INSTALLER_FOLDER%\MoonlightPortable.zip %DEPLOY_FOLDER%\*
|
||||
echo Building MSI
|
||||
msbuild %SOURCE_ROOT%\wix\Moonlight\Moonlight.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=%ARCH%
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%SIGN%"=="1" (
|
||||
echo Signing installer binary
|
||||
echo Signing MSI
|
||||
signtool %SIGNTOOL_PARAMS% %BUILD_FOLDER%\Moonlight.msi
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
echo Building bundle
|
||||
set VCREDIST_INSTALLER=%VCREDIST_PATH%\vcredist_%ARCH%.exe
|
||||
rem Bundles are always x86 binaries
|
||||
msbuild %SOURCE_ROOT%\wix\MoonlightSetup\MoonlightSetup.wixproj /p:Configuration=%BUILD_CONFIG% /p:Platform=x86
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if "%SIGN%"=="1" (
|
||||
echo Signing bundle
|
||||
"%WIX%\bin\insignia" -ib %INSTALLER_FOLDER%\MoonlightSetup.exe -o %BUILD_FOLDER%\engine.exe
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
signtool %SIGNTOOL_PARAMS% %BUILD_FOLDER%\engine.exe
|
||||
@@ -113,6 +116,16 @@ if "%SIGN%"=="1" (
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
)
|
||||
|
||||
echo Building portable package
|
||||
rem This must be done after WiX harvesting and signing, since the VCRT dlls are MS signed
|
||||
rem and should not be harvested for inclusion in the full installer
|
||||
copy "%VCREDIST_PATH%\%ARCH%\Microsoft.VC141.CRT\*.dll" %DEPLOY_FOLDER%
|
||||
rem This file tells Moonlight that it's a portable installation
|
||||
echo. > %DEPLOY_FOLDER%\portable.dat
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
7z a %INSTALLER_FOLDER%\MoonlightPortable.zip %DEPLOY_FOLDER%\*
|
||||
if !ERRORLEVEL! NEQ 0 goto Error
|
||||
|
||||
if /i "%APPVEYOR%"=="true" (
|
||||
echo Pushing artifacts
|
||||
appveyor PushArtifact %INSTALLER_FOLDER%\MoonlightSetup.exe -FileName MoonlightSetup-%ARCH%-%BUILD_CONFIG%.exe
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<OutputName>Moonlight</OutputName>
|
||||
<OutputType>Package</OutputType>
|
||||
<DefineSolutionProperties>false</DefineSolutionProperties>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DefineConstants>Debug</DefineConstants>
|
||||
@@ -26,6 +27,10 @@
|
||||
<HintPath>$(WixExtDir)\WixFirewallExtension.dll</HintPath>
|
||||
<Name>WixFirewallExtension</Name>
|
||||
</WixExtension>
|
||||
<WixExtension Include="WixUtilExtension">
|
||||
<HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
|
||||
<Name>WixUtilExtension</Name>
|
||||
</WixExtension>
|
||||
</ItemGroup>
|
||||
<Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<?define InstallFolder = "Moonlight Game Streaming" ?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
|
||||
xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension"
|
||||
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
|
||||
<Product Id="*"
|
||||
Name="$(var.FullName)"
|
||||
@@ -20,7 +21,7 @@
|
||||
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallInitialize" />
|
||||
<MediaTemplate CompressionLevel="high" />
|
||||
<MediaTemplate CompressionLevel="high" EmbedCab="yes" />
|
||||
|
||||
<?if $(var.Platform) = x64 ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
@@ -38,6 +39,8 @@
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Property Id="APPDATAFOLDER">%LOCALAPPDATA%\Moonlight Game Streaming</Property>
|
||||
|
||||
<!-- There's no way to delete a registry key on uninstall but not major upgrade, so
|
||||
we have to roll our own deletion via custom action -->
|
||||
<CustomAction Id="DeleteRegistryKey"
|
||||
@@ -65,6 +68,7 @@
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
<RemoveFolder Id="CleanupStartMenuShortcut" Directory="ApplicationProgramsFolder" On="uninstall" />
|
||||
<RemoveFolder Id="CleanupDesktopShortcut" Directory="DesktopFolder" On="uninstall" />
|
||||
<util:RemoveFolderEx Id="CleanupAppDataFolder" On="uninstall" Property="APPDATAFOLDER" />
|
||||
<RegistryValue Root="HKCU" Key="Software\Moonlight Game Streaming Project" Name="Installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<OutputName>MoonlightSetup</OutputName>
|
||||
<OutputType>Bundle</OutputType>
|
||||
<DefineSolutionProperties>false</DefineSolutionProperties>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DefineConstants>Debug</DefineConstants>
|
||||
|
||||
Reference in New Issue
Block a user