moonlight-qt/app/streaming/video/ffmpeg-renderers/sdlvid.cpp

346 lines
11 KiB
C++
Raw Normal View History

2019-02-13 03:58:36 +00:00
#include "sdlvid.h"
2018-07-13 09:28:10 +00:00
2019-02-13 05:55:15 +00:00
#include "streaming/session.h"
#include "path.h"
#include <QDir>
2019-02-13 05:55:15 +00:00
#include <Limelight.h>
2018-07-13 09:28:10 +00:00
SdlRenderer::SdlRenderer()
: m_Renderer(nullptr),
m_Texture(nullptr),
m_SwPixelFormat(AV_PIX_FMT_NONE),
m_FontData(Path::readDataFile("ModeSeven.ttf"))
2018-07-13 09:28:10 +00:00
{
2019-02-13 05:55:15 +00:00
SDL_assert(TTF_WasInit() == 0);
if (TTF_Init() != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"TTF_Init() failed: %s",
TTF_GetError());
return;
}
SDL_zero(m_OverlayFonts);
SDL_zero(m_OverlaySurfaces);
SDL_zero(m_OverlayTextures);
2018-07-13 09:28:10 +00:00
}
SdlRenderer::~SdlRenderer()
{
for (int i = 0; i < Overlay::OverlayMax; i++) {
if (m_OverlayFonts[i] != nullptr) {
TTF_CloseFont(m_OverlayFonts[i]);
}
2019-02-13 05:55:15 +00:00
}
TTF_Quit();
SDL_assert(TTF_WasInit() == 0);
for (int i = 0; i < Overlay::OverlayMax; i++) {
if (m_OverlayTextures[i] != nullptr) {
SDL_DestroyTexture(m_OverlayTextures[i]);
}
2019-02-13 05:55:15 +00:00
}
for (int i = 0; i < Overlay::OverlayMax; i++) {
if (m_OverlaySurfaces[i] != nullptr) {
SDL_FreeSurface(m_OverlaySurfaces[i]);
}
2019-02-13 05:55:15 +00:00
}
2018-07-13 09:28:10 +00:00
if (m_Texture != nullptr) {
SDL_DestroyTexture(m_Texture);
}
if (m_Renderer != nullptr) {
SDL_DestroyRenderer(m_Renderer);
}
}
bool SdlRenderer::prepareDecoderContext(AVCodecContext*)
{
/* Nothing to do */
2018-07-28 23:06:26 +00:00
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Using SDL renderer");
2018-07-28 23:06:26 +00:00
2018-07-13 09:28:10 +00:00
return true;
}
2019-02-13 05:55:15 +00:00
void SdlRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
{
// Construct the required font to render the overlay
if (m_OverlayFonts[type] == nullptr) {
if (m_FontData.isEmpty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL overlay font failed to load");
return;
2019-02-13 05:55:15 +00:00
}
// m_FontData must stay around until the font is closed
m_OverlayFonts[type] = TTF_OpenFontRW(SDL_RWFromConstMem(m_FontData.constData(), m_FontData.size()),
1,
Session::get()->getOverlayManager().getOverlayFontSize(type));
if (m_OverlayFonts[type] == nullptr) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"TTF_OpenFont() failed: %s",
TTF_GetError());
2019-02-13 05:55:15 +00:00
// Can't proceed without a font
return;
2019-02-13 05:55:15 +00:00
}
}
2019-02-13 05:55:15 +00:00
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicGetPtr((void**)&m_OverlaySurfaces[type]);
// Free the old surface
if (oldSurface != nullptr && SDL_AtomicCASPtr((void**)&m_OverlaySurfaces[type], oldSurface, nullptr)) {
SDL_FreeSurface(oldSurface);
2019-02-13 05:55:15 +00:00
}
if (Session::get()->getOverlayManager().isOverlayEnabled(type)) {
// The _Wrapped variant is required for line breaks to work
SDL_Surface* surface = TTF_RenderText_Blended_Wrapped(m_OverlayFonts[type],
Session::get()->getOverlayManager().getOverlayText(type),
Session::get()->getOverlayManager().getOverlayColor(type),
1000);
SDL_AtomicSetPtr((void**)&m_OverlaySurfaces[type], surface);
2019-02-13 05:55:15 +00:00
}
}
bool SdlRenderer::isRenderThreadSupported()
{
SDL_RendererInfo info;
SDL_GetRendererInfo(m_Renderer, &info);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"SDL renderer backend: %s",
info.name);
if (info.name != QString("direct3d")) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"SDL renderer backend requires main thread rendering");
return false;
}
return true;
}
bool SdlRenderer::initialize(PDECODER_PARAMETERS params)
2018-07-13 09:28:10 +00:00
{
Uint32 rendererFlags = SDL_RENDERER_ACCELERATED;
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
// SDL doesn't support rendering YUV 10-bit textures yet
return false;
}
if ((SDL_GetWindowFlags(params->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 (params->enableVsync) {
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
}
}
#ifdef Q_OS_WIN32
// We render on a different thread than the main thread which is handling window
// messages. Without D3DCREATE_MULTITHREADED, this can cause a deadlock by blocking
// on a window message being processed while the main thread is blocked waiting for
// the render thread to finish.
SDL_SetHintWithPriority(SDL_HINT_RENDER_DIRECT3D_THREADSAFE, "1", SDL_HINT_OVERRIDE);
#endif
m_Renderer = SDL_CreateRenderer(params->window, -1, rendererFlags);
2018-07-13 09:28:10 +00:00
if (!m_Renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_CreateRenderer() failed: %s",
SDL_GetError());
return false;
}
// The window may be smaller than the stream size, so ensure our
// logical rendering surface size is equal to the stream size
SDL_RenderSetLogicalSize(m_Renderer, params->width, params->height);
// Draw a black frame until the video stream starts rendering
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(m_Renderer);
SDL_RenderPresent(m_Renderer);
#ifdef Q_OS_WIN32
// 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_SetHintWithPriority(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0", SDL_HINT_OVERRIDE);
#endif
2018-07-13 09:28:10 +00:00
return true;
}
void SdlRenderer::renderOverlay(Overlay::OverlayType type)
{
if (Session::get()->getOverlayManager().isOverlayEnabled(type)) {
// If a new surface has been created for updated overlay data, convert it into a texture.
// NB: We have to do this conversion at render-time because we can only interact
// with the renderer on a single thread.
SDL_Surface* surface = (SDL_Surface*)SDL_AtomicGetPtr((void**)&m_OverlaySurfaces[type]);
if (surface != nullptr && SDL_AtomicCASPtr((void**)&m_OverlaySurfaces[type], surface, nullptr)) {
if (m_OverlayTextures[type] != nullptr) {
SDL_DestroyTexture(m_OverlayTextures[type]);
}
if (type == Overlay::OverlayStatusUpdate) {
// Bottom Left
int unused;
SDL_RenderGetLogicalSize(m_Renderer, &unused, &m_OverlayRects[type].y);
m_OverlayRects[type].x = 0;
m_OverlayRects[type].y -= surface->h;
}
else if (type == Overlay::OverlayDebug) {
// Top left
m_OverlayRects[type].x = 0;
m_OverlayRects[type].y = 0;
}
m_OverlayRects[type].w = surface->w;
m_OverlayRects[type].h = surface->h;
m_OverlayTextures[type] = SDL_CreateTextureFromSurface(m_Renderer, surface);
SDL_FreeSurface(surface);
}
// If we have an overlay texture, render it too
if (m_OverlayTextures[type] != nullptr) {
SDL_RenderCopy(m_Renderer, m_OverlayTextures[type], nullptr, &m_OverlayRects[type]);
}
}
}
void SdlRenderer::renderFrame(AVFrame* frame)
2018-07-13 09:28:10 +00:00
{
int err;
AVFrame* swFrame = nullptr;
if (frame->hw_frames_ctx != nullptr) {
// If we are acting as the frontend for a hardware
// accelerated decoder, we'll need to read the frame
// back to render it.
// Find the native read-back format
if (m_SwPixelFormat == AV_PIX_FMT_NONE) {
auto hwFrameCtx = (AVHWFramesContext*)frame->hw_frames_ctx->data;
m_SwPixelFormat = hwFrameCtx->sw_format;
SDL_assert(m_SwPixelFormat != AV_PIX_FMT_NONE);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Selected read-back format: %d",
m_SwPixelFormat);
}
swFrame = av_frame_alloc();
if (swFrame == nullptr) {
return;
}
swFrame->width = frame->width;
swFrame->height = frame->height;
swFrame->format = m_SwPixelFormat;
err = av_hwframe_transfer_data(swFrame, frame, 0);
if (err != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"av_hwframe_transfer_data() failed: %d",
err);
goto Exit;
}
frame = swFrame;
}
if (m_Texture == nullptr) {
Uint32 sdlFormat;
switch (frame->format)
{
case AV_PIX_FMT_YUV420P:
sdlFormat = SDL_PIXELFORMAT_YV12;
break;
case AV_PIX_FMT_NV12:
sdlFormat = SDL_PIXELFORMAT_NV12;
break;
case AV_PIX_FMT_NV21:
sdlFormat = SDL_PIXELFORMAT_NV21;
break;
default:
SDL_assert(false);
goto Exit;
}
m_Texture = SDL_CreateTexture(m_Renderer,
sdlFormat,
SDL_TEXTUREACCESS_STREAMING,
frame->width,
frame->height);
if (!m_Texture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_CreateTexture() failed: %s",
SDL_GetError());
goto Exit;
}
}
if (frame->format == AV_PIX_FMT_YUV420P) {
SDL_UpdateYUVTexture(m_Texture, nullptr,
frame->data[0],
frame->linesize[0],
frame->data[1],
frame->linesize[1],
frame->data[2],
frame->linesize[2]);
}
else {
char* pixels;
int pitch;
err = SDL_LockTexture(m_Texture, nullptr, (void**)&pixels, &pitch);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_LockTexture() failed: %s",
SDL_GetError());
goto Exit;
}
memcpy(pixels,
frame->data[0],
frame->linesize[0] * frame->height);
memcpy(pixels + (frame->linesize[0] * frame->height),
frame->data[1],
frame->linesize[1] * frame->height / 2);
SDL_UnlockTexture(m_Texture);
}
2018-07-13 09:28:10 +00:00
SDL_RenderClear(m_Renderer);
2019-02-13 05:55:15 +00:00
// Draw the video content itself
2018-07-13 09:28:10 +00:00
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
2019-02-13 05:55:15 +00:00
// Draw the overlays
for (int i = 0; i < Overlay::OverlayMax; i++) {
renderOverlay((Overlay::OverlayType)i);
2019-02-13 05:55:15 +00:00
}
2018-07-13 09:28:10 +00:00
SDL_RenderPresent(m_Renderer);
Exit:
if (swFrame != nullptr) {
av_frame_free(&swFrame);
}
2018-07-13 09:28:10 +00:00
}