diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.cpp b/app/streaming/video/ffmpeg-renderers/vdpau.cpp index a903d890..fb2b291e 100644 --- a/app/streaming/video/ffmpeg-renderers/vdpau.cpp +++ b/app/streaming/video/ffmpeg-renderers/vdpau.cpp @@ -1,3 +1,4 @@ +#include #include "vdpau.h" #include @@ -27,9 +28,11 @@ VDPAURenderer::VDPAURenderer() m_PresentationQueueTarget(0), m_PresentationQueue(0), m_VideoMixer(0), + m_OverlayMutex(nullptr), m_NextSurfaceIndex(0) { SDL_zero(m_OutputSurface); + SDL_zero(m_OverlaySurface); } VDPAURenderer::~VDPAURenderer() @@ -52,6 +55,16 @@ VDPAURenderer::~VDPAURenderer() } } + for (int i = 0; i < Overlay::OverlayMax; i++) { + if (m_OverlaySurface[i] != 0) { + m_VdpBitmapSurfaceDestroy(m_OverlaySurface[i]); + } + } + + if (m_OverlayMutex != nullptr) { + SDL_DestroyMutex(m_OverlayMutex); + } + // This must be done last as it frees VDPAU context required to call // the functions above. if (m_HwContext != nullptr) { @@ -136,6 +149,10 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params) GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, &m_VdpOutputSurfaceCreate); GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY, &m_VdpOutputSurfaceDestroy); GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_CAPABILITIES, &m_VdpOutputSurfaceQueryCapabilities); + GET_PROC_ADDRESS(VDP_FUNC_ID_BITMAP_SURFACE_CREATE, &m_VdpBitmapSurfaceCreate); + GET_PROC_ADDRESS(VDP_FUNC_ID_BITMAP_SURFACE_DESTROY, &m_VdpBitmapSurfaceDestroy); + GET_PROC_ADDRESS(VDP_FUNC_ID_BITMAP_SURFACE_PUT_BITS_NATIVE, &m_VdpBitmapSurfacePutBitsNative); + GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_BITMAP_SURFACE, &m_VdpOutputSurfaceRenderBitmapSurface); GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, &m_VdpVideoSurfaceGetParameters); GET_PROC_ADDRESS(VDP_FUNC_ID_GET_INFORMATION_STRING, &m_VdpGetInformationString); @@ -263,6 +280,24 @@ bool VDPAURenderer::initialize(PDECODER_PARAMETERS params) VdpColor color = {0.0, 0.0, 0.0, 1.0}; m_VdpPresentationQueueSetBackgroundColor(m_PresentationQueue, &color); + // Populate blend state for overlays + m_OverlayBlendState.struct_version = VDP_OUTPUT_SURFACE_RENDER_BLEND_STATE_VERSION; + m_OverlayBlendState.blend_factor_destination_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + m_OverlayBlendState.blend_factor_destination_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + m_OverlayBlendState.blend_factor_source_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA; + m_OverlayBlendState.blend_factor_source_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA; + m_OverlayBlendState.blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD; + m_OverlayBlendState.blend_equation_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD; + m_OverlayBlendState.blend_constant = {}; + + // Allocate mutex to synchronize overlay updates and rendering + m_OverlayMutex = SDL_CreateMutex(); + if (m_OverlayMutex == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to create overlay mutex"); + return false; + } + return true; } @@ -284,6 +319,98 @@ bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary* return true; } +void VDPAURenderer::notifyOverlayUpdated(Overlay::OverlayType type) +{ + VdpStatus status; + + SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type); + if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) { + // There's no updated surface and the overlay is enabled, so just leave the old surface alone. + return; + } + + // Destroy the old surface + // NB: The mutex ensures the surface is not currently being read for rendering. + // NB 2: It is safe to unlock here because this thread is the only surface producer. + SDL_LockMutex(m_OverlayMutex); + VdpBitmapSurface oldBitmapSurface = m_OverlaySurface[type]; + m_OverlaySurface[type] = 0; + SDL_UnlockMutex(m_OverlayMutex); + + if (oldBitmapSurface != 0) { + status = m_VdpBitmapSurfaceDestroy(oldBitmapSurface); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpBitmapSurfaceDestroy() failed: %s", + m_VdpGetErrorString(status)); + + // This should never happen. + SDL_assert(false); + } + } + + if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) { + SDL_FreeSurface(newSurface); + return; + } + + if (newSurface != nullptr) { + SDL_assert(!SDL_MUSTLOCK(newSurface)); + + VdpBitmapSurface newBitmapSurface = 0; + status = m_VdpBitmapSurfaceCreate(m_Device, + VDP_RGBA_FORMAT_B8G8R8A8, + newSurface->w, + newSurface->h, + VDP_TRUE, // Is this correct? + &newBitmapSurface); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpBitmapSurfaceCreate() failed: %s", + m_VdpGetErrorString(status)); + SDL_FreeSurface(newSurface); + return; + } + + status = m_VdpBitmapSurfacePutBitsNative(newBitmapSurface, + &newSurface->pixels, + (const uint32_t*)&newSurface->pitch, + nullptr); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpBitmapSurfacePutBitsNative() failed: %s", + m_VdpGetErrorString(status)); + m_VdpBitmapSurfaceDestroy(newBitmapSurface); + SDL_FreeSurface(newSurface); + return; + } + + // Surface data is no longer needed + SDL_FreeSurface(newSurface); + + VdpRect overlayRect; + + if (type == Overlay::OverlayStatusUpdate) { + // Bottom Left + overlayRect.x0 = 0; + overlayRect.y0 = m_DisplayHeight - newSurface->h; + } + else if (type == Overlay::OverlayDebug) { + // Top left + overlayRect.x0 = 0; + overlayRect.y0 = 0; + } + + overlayRect.x1 = overlayRect.x0 + newSurface->w; + overlayRect.y1 = overlayRect.y0 + newSurface->h; + + SDL_LockMutex(m_OverlayMutex); + m_OverlaySurface[type] = newBitmapSurface; + m_OverlayRect[type] = overlayRect; + SDL_UnlockMutex(m_OverlayMutex); + } +} + bool VDPAURenderer::needsTestFrame() { // We need a test frame to see if this VDPAU driver @@ -300,6 +427,44 @@ int VDPAURenderer::getDecoderColorspace() return COLORSPACE_REC_601; } +void VDPAURenderer::renderOverlay(VdpOutputSurface destination, Overlay::OverlayType type) +{ + VdpStatus status; + + // Don't even bother locking the mutex if the overlay is disabled + if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) { + return; + } + + if (SDL_TryLockMutex(m_OverlayMutex) != 0) { + // If the overlay is currently being updated, skip rendering it this frame. + return; + } + + // Check if there's a surface to render + if (m_OverlaySurface[type] == 0) { + SDL_UnlockMutex(m_OverlayMutex); + return; + } + + status = m_VdpOutputSurfaceRenderBitmapSurface(destination, + &m_OverlayRect[type], + m_OverlaySurface[type], + nullptr, + nullptr, + &m_OverlayBlendState, + 0); + + SDL_UnlockMutex(m_OverlayMutex); + + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpOutputSurfaceRenderBitmapSurface() failed: %s", + m_VdpGetErrorString(status)); + return; + } +} + void VDPAURenderer::renderFrame(AVFrame* frame) { if (frame == nullptr) { @@ -400,6 +565,11 @@ void VDPAURenderer::renderFrame(AVFrame* frame) return; } + // Render overlays into the output surface before display + for (int i = 0; i < Overlay::OverlayMax; i++) { + renderOverlay(chosenSurface, (Overlay::OverlayType)i); + } + // Queue the frame for display immediately status = m_VdpPresentationQueueDisplay(m_PresentationQueue, chosenSurface, 0, 0, 0); if (status != VDP_STATUS_OK) { diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.h b/app/streaming/video/ffmpeg-renderers/vdpau.h index 3260936a..4e57ca0d 100644 --- a/app/streaming/video/ffmpeg-renderers/vdpau.h +++ b/app/streaming/video/ffmpeg-renderers/vdpau.h @@ -15,11 +15,14 @@ public: virtual ~VDPAURenderer() override; virtual bool initialize(PDECODER_PARAMETERS params) override; virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; + virtual void notifyOverlayUpdated(Overlay::OverlayType type) override; virtual void renderFrame(AVFrame* frame) override; virtual bool needsTestFrame() override; virtual int getDecoderColorspace() override; private: + void renderOverlay(VdpOutputSurface destination, Overlay::OverlayType type); + uint32_t m_VideoWidth, m_VideoHeight; uint32_t m_DisplayWidth, m_DisplayHeight; AVBufferRef* m_HwContext; @@ -29,6 +32,15 @@ private: VdpRGBAFormat m_OutputSurfaceFormat; VdpDevice m_Device; + // We just have a single mutex to protect all overlay slots. + // This is fine because the majority of time spent in the mutex + // is by the render thread, which cannot contend with itself + // because overlays are rendered sequentially. + SDL_mutex* m_OverlayMutex; + VdpBitmapSurface m_OverlaySurface[Overlay::OverlayMax]; + VdpRect m_OverlayRect[Overlay::OverlayMax]; + VdpOutputSurfaceRenderBlendState m_OverlayBlendState; + #define OUTPUT_SURFACE_COUNT 3 VdpOutputSurface m_OutputSurface[OUTPUT_SURFACE_COUNT]; int m_NextSurfaceIndex; @@ -50,6 +62,10 @@ private: VdpOutputSurfaceCreate* m_VdpOutputSurfaceCreate; VdpOutputSurfaceDestroy* m_VdpOutputSurfaceDestroy; VdpOutputSurfaceQueryCapabilities* m_VdpOutputSurfaceQueryCapabilities; + VdpBitmapSurfaceCreate* m_VdpBitmapSurfaceCreate; + VdpBitmapSurfaceDestroy* m_VdpBitmapSurfaceDestroy; + VdpBitmapSurfacePutBitsNative* m_VdpBitmapSurfacePutBitsNative; + VdpOutputSurfaceRenderBitmapSurface* m_VdpOutputSurfaceRenderBitmapSurface; VdpVideoSurfaceGetParameters* m_VdpVideoSurfaceGetParameters; VdpGetInformationString* m_VdpGetInformationString;