Compare commits

...
14 Commits
21 changed files with 238 additions and 129 deletions
+2 -1
View File
@@ -1,6 +1,6 @@
# Moonlight PC
[Moonlight PC](https://moonlight-stream.org) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built to run on Windows, Mac, and Linux. This client is the successor to [Moonlight Chrome](https://github.com/moonlight-stream/moonlight-chrome) for streaming on PC.
[Moonlight PC](https://moonlight-stream.org) is an open source PC client for NVIDIA GameStream, as used by the NVIDIA Shield.
Moonlight also has mobile versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [iOS](https://github.com/moonlight-stream/moonlight-ios).
@@ -14,6 +14,7 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
- Hardware accelerated video decoding on Windows, Mac, and Linux
- Supports streaming at up to 120 FPS (high refresh rate monitor recommended)
- Supports streaming at 720p, 1080p, 1440p, 4K, and the client PC's native screen resolution
- HDR streaming support
- 7.1 surround sound audio support
- Support for both pointer capture (for games) and direct mouse control (for remote desktop)
- Support for passing system-wide keyboard shortcuts like Alt+Tab to the host
+20 -2
View File
@@ -1288,6 +1288,7 @@ Flickable {
id: decoderComboBox
textRole: "text"
enabled: !enableHdr.checked
model: ListModel {
id: decoderListModel
ListElement {
@@ -1304,9 +1305,26 @@ Flickable {
}
}
// ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : {
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
onActivated: {
if (enabled) {
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
}
}
// This handles the state of the enableHdr checkbox changing
onEnabledChanged: {
if (enabled) {
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
}
else {
StreamingPreferences.videoDecoderSelection = StreamingPreferences.VDS_AUTO
}
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered && !enabled
ToolTip.text: qsTr("Enabling HDR overrides manual decoder selections.")
}
Label {
+16
View File
@@ -32,6 +32,22 @@ void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
}
}
void StreamUtils::screenSpaceToNormalizedDeviceCoords(SDL_FRect* rect, int viewportWidth, int viewportHeight)
{
rect->x = (rect->x / (viewportWidth / 2.0f)) - 1.0f;
rect->y = (rect->y / (viewportHeight / 2.0f)) - 1.0f;
rect->w = rect->w / (viewportWidth / 2.0f);
rect->h = rect->h / (viewportHeight / 2.0f);
}
void StreamUtils::screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight)
{
dst->x = ((float)src->x / (viewportWidth / 2.0f)) - 1.0f;
dst->y = ((float)src->y / (viewportHeight / 2.0f)) - 1.0f;
dst->w = (float)src->w / (viewportWidth / 2.0f);
dst->h = (float)src->h / (viewportHeight / 2.0f);
}
int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
{
int displayIndex = SDL_GetWindowDisplayIndex(window);
+17
View File
@@ -2,6 +2,17 @@
#include <SDL.h>
// SDL_FRect wasn't added until 2.0.10
#if !SDL_VERSION_ATLEAST(2, 0, 10)
typedef struct SDL_FRect
{
float x;
float y;
float w;
float h;
} SDL_FRect;
#endif
class StreamUtils
{
public:
@@ -11,6 +22,12 @@ public:
static
void scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst);
static
void screenSpaceToNormalizedDeviceCoords(SDL_FRect* rect, int viewportWidth, int viewportHeight);
static
void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight);
static
bool getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode);
@@ -464,10 +464,6 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
m_FrameWaitableObject = m_SwapChain->GetFrameLatencyWaitableObject();
SDL_assert(m_FrameWaitableObject != nullptr);
// Wait for the swap chain to be ready. This is required because we don't
// we're waiting after presenting in the general case, not before.
WaitForSingleObjectEx(m_FrameWaitableObject, 1000, FALSE);
}
else {
IDXGIDevice1* dxgiDevice;
@@ -582,13 +578,24 @@ void D3D11VARenderer::setHdrMode(bool enabled)
unlockContext(this);
}
void D3D11VARenderer::waitToRender()
{
if (m_FrameWaitableObject != nullptr) {
SDL_assert(m_Windowed);
SDL_assert(m_DecoderParams.enableVsync);
// Wait for the pipeline to be ready for the next frame in V-Sync mode.
//
// This callback happens before selecting the next frame to render, so
// we can wait for the previous frame to finish prior to picking the
// next one to display. This reduces the effective display latency
// by ensuring we always render the most recent frame immediately.
WaitForSingleObjectEx(m_FrameWaitableObject, 500, FALSE);
}
}
void D3D11VARenderer::renderFrame(AVFrame* frame)
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
// Acquire the context lock for rendering to prevent concurrent
// access from inside FFmpeg's decoding code
lockContext(this);
@@ -674,22 +681,6 @@ void D3D11VARenderer::renderFrame(AVFrame* frame)
SDL_PushEvent(&event);
return;
}
if (m_FrameWaitableObject != nullptr) {
SDL_assert(m_Windowed);
SDL_assert(m_DecoderParams.enableVsync);
// Wait for the pipeline to be ready for the next frame in V-Sync mode.
//
// MSDN advises us to wait *before* doing any rendering operations,
// however that assumes the a typical game which will latch inputs,
// run the engine, draw, etc. after WaitForSingleObjectEx(). In our case,
// we actually want wait *after* our rendering operations, because our AVFrame
// is already set in stone by the time we enter this function. Waiting after
// presenting allows a more recent frame to be received before renderFrame()
// is called again.
WaitForSingleObjectEx(m_FrameWaitableObject, 1000, FALSE);
}
}
void D3D11VARenderer::renderOverlay(Overlay::OverlayType type)
@@ -945,12 +936,7 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
renderRect.h = newSurface->h;
// Convert screen space to normalized device coordinates
renderRect.x /= m_DisplayWidth / 2;
renderRect.w /= m_DisplayWidth / 2;
renderRect.y /= m_DisplayHeight / 2;
renderRect.h /= m_DisplayHeight / 2;
renderRect.x -= 1.0f;
renderRect.y -= 1.0f;
StreamUtils::screenSpaceToNormalizedDeviceCoords(&renderRect, m_DisplayWidth, m_DisplayHeight);
// The surface is no longer required
SDL_FreeSurface(newSurface);
@@ -1290,10 +1276,7 @@ bool D3D11VARenderer::setupRenderingResources()
// Convert screen space to normalized device coordinates
SDL_FRect renderRect;
renderRect.x = ((float)dst.x / (m_DisplayWidth / 2)) - 1.0f;
renderRect.y = ((float)dst.y / (m_DisplayHeight / 2)) - 1.0f;
renderRect.w = (float)dst.w / (m_DisplayWidth / 2);
renderRect.h = (float)dst.h / (m_DisplayHeight / 2);
StreamUtils::screenSpaceToNormalizedDeviceCoords(&dst, &renderRect, m_DisplayWidth, m_DisplayHeight);
// Don't sample from the alignment padding area since that's not part of the video
SDL_assert(m_TextureAlignment != 0);
@@ -19,6 +19,7 @@ public:
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override;
virtual void renderFrame(AVFrame* frame) override;
virtual void waitToRender() override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual void setHdrMode(bool enabled) override;
virtual int getRendererAttributes() override;
@@ -475,11 +475,6 @@ void DrmRenderer::renderFrame(AVFrame* frame)
AVDRMFrameDescriptor mappedFrame;
AVDRMFrameDescriptor* drmFrame;
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
// If we are acting as the frontend renderer, we'll need to have the backend
// map this frame into a DRM PRIME descriptor that we can render.
if (m_BackendRenderer != nullptr) {
@@ -978,11 +978,6 @@ int DXVA2Renderer::getDecoderColorspace()
void DXVA2Renderer::renderFrame(AVFrame *frame)
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
HRESULT hr;
+81 -28
View File
@@ -76,11 +76,16 @@ EGLRenderer::EGLRenderer(IFFmpegRenderer *backendRenderer)
m_Backend(backendRenderer),
m_VAO(0),
m_BlockingSwapBuffers(false),
m_LastRenderSync(EGL_NO_SYNC),
m_LastFrame(av_frame_alloc()),
m_glEGLImageTargetTexture2DOES(nullptr),
m_glGenVertexArraysOES(nullptr),
m_glBindVertexArrayOES(nullptr),
m_glDeleteVertexArraysOES(nullptr),
m_eglCreateSync(nullptr),
m_eglCreateSyncKHR(nullptr),
m_eglDestroySync(nullptr),
m_eglClientWaitSync(nullptr),
m_GlesMajorVersion(0),
m_GlesMinorVersion(0),
m_HasExtUnpackSubimage(false),
@@ -100,6 +105,10 @@ EGLRenderer::~EGLRenderer()
if (m_Context) {
// Reattach the GL context to the main thread for destruction
SDL_GL_MakeCurrent(m_Window, m_Context);
if (m_LastRenderSync != EGL_NO_SYNC) {
SDL_assert(m_eglDestroySync != nullptr);
m_eglDestroySync(m_EGLDisplay, m_LastRenderSync);
}
if (m_ShaderProgram) {
glDeleteProgram(m_ShaderProgram);
}
@@ -214,13 +223,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type)
free(packedPixelData);
}
// SDL_FRect wasn't added until 2.0.10
struct {
float x;
float y;
float w;
float h;
} overlayRect = {};
SDL_FRect overlayRect;
// These overlay positions differ from the other renderers because OpenGL
// places the origin in the lower-left corner instead of the upper-left.
@@ -243,12 +246,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type)
SDL_FreeSurface(newSurface);
// Convert screen space to normalized device coordinates
overlayRect.x /= m_ViewportWidth / 2;
overlayRect.w /= m_ViewportWidth / 2;
overlayRect.y /= m_ViewportHeight / 2;
overlayRect.h /= m_ViewportHeight / 2;
overlayRect.x -= 1.0f;
overlayRect.y -= 1.0f;
StreamUtils::screenSpaceToNormalizedDeviceCoords(&overlayRect, m_ViewportWidth, m_ViewportHeight);
OVERLAY_VERTEX verts[] =
{
@@ -626,6 +624,30 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
return false;
}
// EGL_KHR_fence_sync is an extension for EGL 1.1+
if (eglExtensions.isSupported("EGL_KHR_fence_sync")) {
// eglCreateSyncKHR() has a slightly different prototype to eglCreateSync()
m_eglCreateSyncKHR = (typeof(m_eglCreateSyncKHR))eglGetProcAddress("eglCreateSyncKHR");
m_eglDestroySync = (typeof(m_eglDestroySync))eglGetProcAddress("eglDestroySyncKHR");
m_eglClientWaitSync = (typeof(m_eglClientWaitSync))eglGetProcAddress("eglClientWaitSyncKHR");
}
else {
// EGL 1.5 introduced sync support to the core specification
m_eglCreateSync = (typeof(m_eglCreateSync))eglGetProcAddress("eglCreateSync");
m_eglDestroySync = (typeof(m_eglDestroySync))eglGetProcAddress("eglDestroySync");
m_eglClientWaitSync = (typeof(m_eglClientWaitSync))eglGetProcAddress("eglClientWaitSync");
}
if (!(m_eglCreateSync || m_eglCreateSyncKHR) || !m_eglDestroySync || !m_eglClientWaitSync) {
EGL_LOG(Warn, "Failed to find sync functions");
// Sub-optimal, but not fatal
m_eglCreateSync = nullptr;
m_eglCreateSyncKHR = nullptr;
m_eglDestroySync = nullptr;
m_eglClientWaitSync = nullptr;
}
/* Compute the video region size in order to keep the aspect ratio of the
* video stream.
*/
@@ -825,16 +847,28 @@ bool EGLRenderer::specialize() {
return err == GL_NO_ERROR;
}
void EGLRenderer::cleanupRenderContext()
{
// Detach the context from the render thread so the destructor can attach it
SDL_GL_MakeCurrent(m_Window, nullptr);
}
void EGLRenderer::waitToRender()
{
// Ensure our GL context is active on this thread
// See comment in renderFrame() for more details.
SDL_GL_MakeCurrent(m_Window, m_Context);
if (m_LastRenderSync != 0) {
SDL_assert(m_eglClientWaitSync != nullptr);
m_eglClientWaitSync(m_EGLDisplay, m_LastRenderSync, EGL_SYNC_FLUSH_COMMANDS_BIT, EGL_FOREVER);
}
}
void EGLRenderer::renderFrame(AVFrame* frame)
{
EGLImage imgs[EGL_MAX_PLANES];
if (frame == nullptr) {
// End of stream - unbind the GL context
SDL_GL_MakeCurrent(m_Window, nullptr);
return;
}
// Attach our GL context to the render thread
// NB: It should already be current, unless the SDL render event watcher
// performs a rendering operation (like a viewport update on resize) on
@@ -889,15 +923,34 @@ void EGLRenderer::renderFrame(AVFrame* frame)
SDL_GL_SwapWindow(m_Window);
if (m_BlockingSwapBuffers) {
// This glClear() forces us to block until the buffer swap is
// complete to continue rendering. Mesa won't actually wait
// for the swap with just glFinish() alone. Waiting here keeps us
// in lock step with the display refresh rate. If we don't wait
// here, we'll stall on the first GL call next frame. Doing the
// wait here instead allows more time for a newer frame to arrive
// for next renderFrame() call.
glClear(GL_COLOR_BUFFER_BIT);
glFinish();
// If we this EGL implementation supports fences, use those to delay
// rendering the next frame until this one is completed.
if (m_eglClientWaitSync != nullptr) {
// Delete the sync object from last render
if (m_LastRenderSync != EGL_NO_SYNC) {
m_eglDestroySync(m_EGLDisplay, m_LastRenderSync);
}
// Create a new sync object that will be signalled when the buffer swap is completed
if (m_eglCreateSync != nullptr) {
m_LastRenderSync = m_eglCreateSync(m_EGLDisplay, EGL_SYNC_FENCE, nullptr);
}
else {
SDL_assert(m_eglCreateSyncKHR != nullptr);
m_LastRenderSync = m_eglCreateSyncKHR(m_EGLDisplay, EGL_SYNC_FENCE, nullptr);
}
}
else {
// This glClear() forces us to block until the buffer swap is
// complete to continue rendering. Mesa won't actually wait
// for the swap with just glFinish() alone. Waiting here keeps us
// in lock step with the display refresh rate. If we don't wait
// here, we'll stall on the first GL call next frame. Doing the
// wait here instead allows more time for a newer frame to arrive
// for next renderFrame() call.
glClear(GL_COLOR_BUFFER_BIT);
glFinish();
}
}
m_Backend->freeEGLImages(m_EGLDisplay, imgs);
@@ -11,6 +11,8 @@ public:
virtual ~EGLRenderer() override;
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override;
virtual void cleanupRenderContext() override;
virtual void waitToRender() override;
virtual void renderFrame(AVFrame* frame) override;
virtual bool testRenderFrame(AVFrame* frame) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
@@ -44,11 +46,16 @@ private:
IFFmpegRenderer *m_Backend;
unsigned int m_VAO;
bool m_BlockingSwapBuffers;
EGLSync m_LastRenderSync;
AVFrame* m_LastFrame;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC m_glEGLImageTargetTexture2DOES;
PFNGLGENVERTEXARRAYSOESPROC m_glGenVertexArraysOES;
PFNGLBINDVERTEXARRAYOESPROC m_glBindVertexArrayOES;
PFNGLDELETEVERTEXARRAYSOESPROC m_glDeleteVertexArraysOES;
PFNEGLCREATESYNCPROC m_eglCreateSync;
PFNEGLCREATESYNCKHRPROC m_eglCreateSyncKHR;
PFNEGLDESTROYSYNCPROC m_eglDestroySync;
PFNEGLCLIENTWAITSYNCPROC m_eglClientWaitSync;
int m_GlesMajorVersion;
int m_GlesMinorVersion;
bool m_HasExtUnpackSubimage;
@@ -345,11 +345,6 @@ bool MmalRenderer::needsTestFrame()
void MmalRenderer::renderFrame(AVFrame* frame)
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
MMAL_BUFFER_HEADER_T* buffer = (MMAL_BUFFER_HEADER_T*)frame->data[3];
MMAL_STATUS_T status;
@@ -46,8 +46,9 @@ Pacer::~Pacer()
SDL_WaitThread(m_RenderThread, nullptr);
}
else {
// Send a null AVFrame to indicate end of stream on the main thread
m_VsyncRenderer->renderFrame(nullptr);
// Notify the renderer that it is being destroyed soon
// NB: This must happen on the same thread that calls renderFrame().
m_VsyncRenderer->cleanupRenderContext();
}
// Delete any remaining unconsumed frames
@@ -90,6 +91,9 @@ int Pacer::renderThread(void* context)
}
while (!me->m_Stopping) {
// Wait for the renderer to be ready for the next frame
me->m_VsyncRenderer->waitToRender();
// Acquire the frame queue lock to protect the queue and
// the not empty condition
me->m_FrameQueueLock.lock();
@@ -110,8 +114,9 @@ int Pacer::renderThread(void* context)
me->renderLastFrameAndUnlock();
}
// Send a null AVFrame to indicate end of stream on the render thread
me->m_VsyncRenderer->renderFrame(nullptr);
// Notify the renderer that it is being destroyed soon
// NB: This must happen on the same thread that calls renderFrame().
me->m_VsyncRenderer->cleanupRenderContext();
return 0;
}
@@ -21,9 +21,19 @@ extern "C" {
#ifndef EGL_VERSION_1_5
typedef intptr_t EGLAttrib;
typedef void *EGLImage;
typedef void *EGLSync;
#define EGL_NO_SYNC ((EGLSync)0)
#define EGL_SYNC_FENCE 0x30F9
#define EGL_FOREVER 0xFFFFFFFFFFFFFFFFull
#define EGL_SYNC_FLUSH_COMMANDS_BIT 0x0001
#endif
#if !defined(EGL_VERSION_1_5) || !defined(EGL_EGL_PROTOTYPES)
typedef EGLSync (EGLAPIENTRYP PFNEGLCREATESYNCPROC) (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCPROC) (EGLDisplay dpy, EGLSync sync);
typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
typedef EGLImage (EGLAPIENTRYP PFNEGLCREATEIMAGEPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list);
typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEPROC) (EGLDisplay dpy, EGLImage image);
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC) (EGLenum platform, void *native_display, const EGLAttrib *attrib_list);
@@ -40,6 +50,10 @@ typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGL
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list);
#endif
#if !defined(EGL_KHR_fence_sync) || !defined(EGL_EGLEXT_PROTOTYPES)
typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list);
#endif
#ifndef EGL_EXT_image_dma_buf_import
#define EGL_LINUX_DMA_BUF_EXT 0x3270
#define EGL_LINUX_DRM_FOURCC_EXT 0x3271
@@ -102,6 +116,18 @@ public:
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) = 0;
virtual void renderFrame(AVFrame* frame) = 0;
// Called for threaded renderers to allow them to wait prior to us latching
// the next frame for rendering (as opposed to waiting on buffer swap with
// an older frame already queued for display).
virtual void waitToRender() {
// Don't wait by default
}
// Called on the same thread as renderFrame() during destruction of the renderer
virtual void cleanupRenderContext() {
// Nothing
}
virtual bool testRenderFrame(AVFrame*) {
// If the renderer doesn't provide an explicit test routine,
// we will always assume that any returned AVFrame can be
@@ -194,7 +220,7 @@ public:
virtual void freeEGLImages(EGLDisplay, EGLImage[EGL_MAX_PLANES]) {}
#endif
#if HAVE_DRM
#ifdef HAVE_DRM
// By default we can't do DRM PRIME export
virtual bool canExportDrmPrime() {
return false;
@@ -344,11 +344,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
int err;
AVFrame* swFrame = nullptr;
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
if (frame->hw_frames_ctx != nullptr && frame->format != AV_PIX_FMT_CUDA) {
#ifdef HAVE_CUDA
ReadbackRetry:
@@ -351,11 +351,6 @@ int VAAPIRenderer::getDecoderColorspace()
void
VAAPIRenderer::renderFrame(AVFrame* frame)
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
VASurfaceID surface = (VASurfaceID)(uintptr_t)frame->data[3];
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
AVVAAPIDeviceContext* vaDeviceContext = (AVVAAPIDeviceContext*)deviceContext->hwctx;
@@ -415,6 +410,8 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
}
}
#if defined(HAVE_EGL) || defined(HAVE_DRM)
// Ensure that vaExportSurfaceHandle() is supported by the VA-API driver
bool
VAAPIRenderer::canExportSurfaceHandle(int layerTypeFlag) {
@@ -490,6 +487,8 @@ VAAPIRenderer::canExportSurfaceHandle(int layerTypeFlag) {
return true;
}
#endif
#ifdef HAVE_EGL
bool
+5 -2
View File
@@ -26,7 +26,7 @@ extern "C" {
#include <va/va_drm.h>
#endif
#include <libavutil/hwcontext_vaapi.h>
#ifdef HAVE_EGL
#if defined(HAVE_EGL) || defined(HAVE_DRM)
#include <va/va_drmcommon.h>
#endif
}
@@ -50,7 +50,7 @@ public:
virtual void freeEGLImages(EGLDisplay dpy, EGLImage[EGL_MAX_PLANES]) override;
#endif
#if HAVE_DRM
#ifdef HAVE_DRM
virtual bool canExportDrmPrime() override;
virtual bool mapDrmPrimeFrame(AVFrame* frame, AVDRMFrameDescriptor* drmDescriptor) override;
virtual void unmapDrmPrimeFrame(AVDRMFrameDescriptor* drmDescriptor) override;
@@ -58,7 +58,10 @@ public:
private:
VADisplay openDisplay(SDL_Window* window);
#if defined(HAVE_EGL) || defined(HAVE_DRM)
bool canExportSurfaceHandle(int layerTypeFlag);
#endif
int m_WindowSystem;
AVBufferRef* m_HwContext;
+12 -6
View File
@@ -474,13 +474,17 @@ void VDPAURenderer::renderOverlay(VdpOutputSurface destination, Overlay::Overlay
}
}
void VDPAURenderer::waitToRender()
{
VdpOutputSurface chosenSurface = m_OutputSurface[m_NextSurfaceIndex];
// Wait for the next render target surface to be idle before proceeding
VdpTime pts;
m_VdpPresentationQueueBlockUntilSurfaceIdle(m_PresentationQueue, chosenSurface, &pts);
}
void VDPAURenderer::renderFrame(AVFrame* frame)
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
VdpStatus status;
VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3];
@@ -529,7 +533,9 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
}
}
// Wait for this frame to be off the screen
// Wait for this frame to be off the screen. This will usually be a no-op
// since it already happened in waitToRender(). However, that won't be the
// case is when frame pacing is enabled.
VdpTime pts;
m_VdpPresentationQueueBlockUntilSurfaceIdle(m_PresentationQueue, chosenSurface, &pts);
@@ -16,6 +16,7 @@ public:
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override;
virtual void waitToRender() override;
virtual void renderFrame(AVFrame* frame) override;
virtual bool needsTestFrame() override;
virtual int getDecoderColorspace() override;
+13 -15
View File
@@ -158,14 +158,22 @@ public:
return true;
}
virtual void waitToRender() override
{
if (m_DisplayLink != nullptr) {
// Vsync is enabled, so wait for a swap before returning
SDL_LockMutex(m_VsyncMutex);
if (SDL_CondWaitTimeout(m_VsyncPassed, m_VsyncMutex, 100) == SDL_MUTEX_TIMEDOUT) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"V-sync wait timed out after 100 ms");
}
SDL_UnlockMutex(m_VsyncMutex);
}
}
// Caller frees frame after we return
virtual void renderFrame(AVFrame* frame) override
{
if (frame == nullptr) {
// End of stream - nothing to do for us
return;
}
OSStatus status;
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
@@ -235,16 +243,6 @@ public:
}
}
if (m_DisplayLink != nullptr) {
// Vsync is enabled, so wait for a swap before returning
SDL_LockMutex(m_VsyncMutex);
if (SDL_CondWaitTimeout(m_VsyncPassed, m_VsyncMutex, 100) == SDL_MUTEX_TIMEDOUT) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"V-sync wait timed out after 100 ms");
}
SDL_UnlockMutex(m_VsyncMutex);
}
// Queue this sample for the next v-sync
CMSampleTimingInfo timingInfo = {
.duration = kCMTimeInvalid,
+4 -9
View File
@@ -4,12 +4,6 @@ clone_depth: 1
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
QTDIR: C:\Qt\5.15
- APPVEYOR_BUILD_WORKER_IMAGE: macOS-BigSur
BUILD_TARGET: macos
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu1604
BUILD_TARGET: steamlink
- APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu
BUILD_TARGET: linux
FFMPEG_CONFIGURE_ARGS: --enable-pic --disable-static --enable-shared --disable-all --enable-avcodec --enable-decoder=h264 --enable-decoder=hevc --enable-nvdec --enable-hwaccel=h264_nvdec --enable-hwaccel=hevc_nvdec --enable-hwaccel=h264_vaapi --enable-hwaccel=hevc_vaapi --enable-hwaccel=h264_vdpau --enable-hwaccel=hevc_vdpau
@@ -19,10 +13,11 @@ install:
- sh: '[ "$BUILD_TARGET" != macos ] || npm install --global create-dmg'
- sh: '[ "$BUILD_TARGET" != steamlink ] || sudo apt install -y libc6:i386 libstdc++6:i386'
- sh: '[ "$BUILD_TARGET" != steamlink ] || git clone --depth=1 https://github.com/ValveSoftware/steamlink-sdk.git $HOME/steamlink-sdk'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-bionic'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:cgutman/opt-qt-5.15.2-eglfsgbm-bionic'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo add-apt-repository ppa:savoury1/display'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt update || true'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt install -y qt515base qt515quickcontrols2 qt515svg qt515wayland nasm libgbm-dev libdrm-dev libfreetype6-dev libasound2-dev libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev wayland-protocols libopus-dev libvdpau-dev'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_REV=5a5ba34b0ec55b5563198eff5eb475eb0b16f092 && git clone https://github.com/cgutman/SDL.git SDL2 && cd SDL2 && git checkout $SDL2_REV && ./configure --enable-video-kmsdrm && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: '[ "$BUILD_TARGET" != linux ] || sudo apt install -y qt515base qt515quickcontrols2 qt515svg qt515wayland nasm libgbm-dev libdrm-dev libfreetype6-dev libasound2-dev libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev wayland-protocols libopus-dev libvdpau-dev libdecor-0-dev'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_REV=45372b1c276a748e71c373880fbb14d0a92ef6c1 && git clone https://github.com/libsdl-org/SDL.git SDL2 && cd SDL2 && git checkout $SDL2_REV && ./configure --enable-video-kmsdrm && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export SDL2_TTF_VER=2.0.18 && wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-$SDL2_TTF_VER.tar.gz && tar -xvf SDL2_ttf-$SDL2_TTF_VER.tar.gz && cd SDL2_ttf-$SDL2_TTF_VER && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export LIBVA_VER=2.14.0 && git clone https://github.com/intel/libva.git && cd libva && git checkout $LIBVA_VER && ./autogen.sh && ./configure && make -j$(nproc) && sudo make install && cd ..; fi'
- sh: 'if [[ "$BUILD_TARGET" = linux ]]; then export NVHDR_VER=8.1.24.13 && wget https://github.com/FFmpeg/nv-codec-headers/releases/download/n$NVHDR_VER/nv-codec-headers-$NVHDR_VER.tar.gz && tar -xvf nv-codec-headers-$NVHDR_VER.tar.gz && cd nv-codec-headers-$NVHDR_VER && sudo make install && cd ..; fi'
+1 -1
View File
@@ -42,7 +42,7 @@ popd
echo Creating AppImage
pushd $INSTALLER_FOLDER
VERSION=$VERSION linuxdeployqt $DEPLOY_FOLDER/usr/share/applications/com.moonlight_stream.Moonlight.desktop -qmldir=$SOURCE_ROOT/app/gui -appimage || fail "linuxdeployqt failed!"
VERSION=$VERSION linuxdeployqt $DEPLOY_FOLDER/usr/share/applications/com.moonlight_stream.Moonlight.desktop -qmldir=$SOURCE_ROOT/app/gui -extra-plugins=platforms/libqeglfs.so,platforms/libqwayland-egl.so,platforms/libqwayland-generic.so,egldeviceintegrations,platformthemes,wayland-decoration-client,wayland-shell-integration,wayland-graphics-integration-client -appimage || fail "linuxdeployqt failed!"
popd
echo Build successful