Compare commits
14
Commits
v4.0.0
...
appimage_kms
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b23a36908c | ||
|
|
e3a7b54f90 | ||
|
|
81d5e7f014 | ||
|
|
9790c218dc | ||
|
|
6d3d51553b | ||
|
|
474591c6a5 | ||
|
|
5de0dc2713 | ||
|
|
57a34a7abb | ||
|
|
54517bda1b | ||
|
|
4291e15b60 | ||
|
|
940ae6406b | ||
|
|
6a0cf4bfd3 | ||
|
|
fee6f75223 | ||
|
|
8c93b2d54c |
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user