From 19e8d9d0cf81bca056471d9a1522a96832f7e40c Mon Sep 17 00:00:00 2001 From: Mark Kendall Date: Mon, 29 Jun 2020 17:22:25 +0100 Subject: [PATCH] macos: Handle high DPI displays - behaviour should be unchanged for non-macos installations - the underlying problem is that when macos is using high DPI, the windowing system reports e.g. 1920x1080 as the window size but the underlying OpenGL context is using the full resolution e.g. 3840x2160. - video playback is actually easiest - as we just scale the video window/viewport as needed - and everything else falls into place. - the UI painter is slightly more complicated as our UI images are at the lower resolution - and the entire UI code is operating on the reported window size. So scale incoming rendering data as required, which also requires some tweaking of the texture vertices. Closes #13618 --- mythtv/libs/libmythtv/mythvideoout.cpp | 2 +- mythtv/libs/libmythtv/videooutwindow.cpp | 30 +++++++-- mythtv/libs/libmythtv/videooutwindow.h | 5 ++ mythtv/libs/libmythui/mythpainter.h | 4 +- .../libmythui/opengl/mythpainteropengl.cpp | 62 ++++++++++++++++--- .../libs/libmythui/opengl/mythpainteropengl.h | 9 +++ .../libmythui/opengl/mythrenderopengl.cpp | 10 +-- .../libs/libmythui/opengl/mythrenderopengl.h | 4 +- 8 files changed, 105 insertions(+), 21 deletions(-) diff --git a/mythtv/libs/libmythtv/mythvideoout.cpp b/mythtv/libs/libmythtv/mythvideoout.cpp index 8eb608b53ea..79b408aaf0a 100644 --- a/mythtv/libs/libmythtv/mythvideoout.cpp +++ b/mythtv/libs/libmythtv/mythvideoout.cpp @@ -1019,7 +1019,7 @@ void MythVideoOutput::InitDisplayMeasurements(void) .arg(displayaspect).arg(source)); // Get the window and screen resolutions - QSize window = m_window.GetWindowRect().size(); + QSize window = m_window.GetRawWindowRect().size(); QSize screen = m_display->GetResolution(); // If not running fullscreen, adjust for window size and ignore any video diff --git a/mythtv/libs/libmythtv/videooutwindow.cpp b/mythtv/libs/libmythtv/videooutwindow.cpp index cba4327174a..8aca6995cb3 100644 --- a/mythtv/libs/libmythtv/videooutwindow.cpp +++ b/mythtv/libs/libmythtv/videooutwindow.cpp @@ -38,6 +38,11 @@ #define LOC QString("VideoWin: ") +#define SCALED_RECT(SRC, SCALE) QRect{ static_cast(SRC.left() * SCALE), \ + static_cast(SRC.top() * SCALE), \ + static_cast(SRC.width() * SCALE), \ + static_cast(SRC.height() * SCALE) } + static float fix_aspect(float raw); static float snap(float value, float snapto, float diff); @@ -62,6 +67,14 @@ void VideoOutWindow::ScreenChanged(QScreen */*screen*/) MoveResize(); } +void VideoOutWindow::PhysicalDPIChanged(qreal /*DPI*/) +{ + // PopulateGeometry will update m_devicePixelRatio + PopulateGeometry(); + m_windowRect = m_displayVisibleRect = SCALED_RECT(m_rawWindowRect, m_devicePixelRatio); + MoveResize(); +} + void VideoOutWindow::PopulateGeometry(void) { if (!m_display) @@ -71,6 +84,10 @@ void VideoOutWindow::PopulateGeometry(void) if (!screen) return; +#ifdef Q_OS_MACOS + m_devicePixelRatio = screen->devicePixelRatio(); +#endif + if (MythDisplay::SpanAllScreens() && MythDisplay::GetScreenCount() > 1) { m_screenGeometry = screen->virtualGeometry(); @@ -415,6 +432,9 @@ bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim, { m_display = Display; connect(m_display, &MythDisplay::CurrentScreenChanged, this, &VideoOutWindow::ScreenChanged); +#ifdef Q_OS_MACOS + connect(m_display, &MythDisplay::PhysicalDPIChanged, this, &VideoOutWindow::PhysicalDPIChanged); +#endif } if (m_display) @@ -428,7 +448,8 @@ bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim, // N.B. we are always confined to the window size so use that for the initial // displayVisibleRect - m_windowRect = m_displayVisibleRect = WindowRect; + m_rawWindowRect = WindowRect; + m_windowRect = m_displayVisibleRect = SCALED_RECT(WindowRect, m_devicePixelRatio); int pbp_width = m_displayVisibleRect.width() / 2; if (m_pipState == kPBPLeft || m_pipState == kPBPRight) @@ -612,12 +633,13 @@ void VideoOutWindow::SetDisplayAspect(float DisplayAspect) void VideoOutWindow::SetWindowSize(QSize Size) { - if (Size != m_windowRect.size()) + if (Size != m_rawWindowRect.size()) { - QRect rect(m_windowRect.topLeft(), Size); + QRect rect(m_rawWindowRect.topLeft(), Size); LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New window rect: %1x%2+%3+%4") .arg(rect.width()).arg(rect.height()).arg(rect.left()).arg(rect.top())); - m_windowRect = m_displayVisibleRect = rect; + m_rawWindowRect = rect; + m_windowRect = m_displayVisibleRect = SCALED_RECT(rect, m_devicePixelRatio); MoveResize(); } } diff --git a/mythtv/libs/libmythtv/videooutwindow.h b/mythtv/libs/libmythtv/videooutwindow.h index b5e3ab579e0..50ee9428cb7 100644 --- a/mythtv/libs/libmythtv/videooutwindow.h +++ b/mythtv/libs/libmythtv/videooutwindow.h @@ -45,6 +45,7 @@ class VideoOutWindow : public QObject public slots: void ScreenChanged (QScreen *screen); + void PhysicalDPIChanged (qreal /*DPI*/); // Sets void InputChanged (const QSize &VideoDim, const QSize &VideoDispDim, float Aspect); @@ -74,6 +75,7 @@ class VideoOutWindow : public QObject float GetOverridenVideoAspect(void) const { return m_videoAspectOverride;} QRect GetDisplayVisibleRect(void) const { return m_displayVisibleRect; } QRect GetWindowRect(void) const { return m_windowRect; } + QRect GetRawWindowRect(void) const { return m_rawWindowRect; } QRect GetScreenGeometry(void) const { return m_screenGeometry; } QRect GetVideoRect(void) const { return m_videoRect; } QRect GetDisplayVideoRect(void) const { return m_displayVideoRect; } @@ -113,6 +115,7 @@ class VideoOutWindow : public QObject bool m_dbScalingAllowed {true}; ///< disable this to prevent overscan/underscan bool m_dbUseGUISize {false}; ///< Use the gui size for video window QRect m_screenGeometry {0,0,1024,768}; ///< Full screen geometry + qreal m_devicePixelRatio {1.0}; // Manual Zoom float m_manualVertScale {1.0F}; ///< Manually applied vertical scaling. @@ -145,6 +148,8 @@ class VideoOutWindow : public QObject QRect m_displayVisibleRect {0,0,0,0}; /// Rectangle describing QWidget bounds. QRect m_windowRect {0,0,0,0}; + /// Rectangle describing QWidget bounds - not adjusted for high DPI scaling (macos) + QRect m_rawWindowRect {0,0,0,0}; /// Used to save the display_visible_rect for /// restoration after video embedding ends. QRect m_tmpDisplayVisibleRect {0,0,0,0}; diff --git a/mythtv/libs/libmythui/mythpainter.h b/mythtv/libs/libmythui/mythpainter.h index 56124569970..d7367fb8775 100644 --- a/mythtv/libs/libmythui/mythpainter.h +++ b/mythtv/libs/libmythui/mythpainter.h @@ -29,8 +29,10 @@ class UIEffects; using LayoutVector = QVector; using FormatVector = QVector; -class MUI_PUBLIC MythPainter +class MUI_PUBLIC MythPainter : public QObject { + Q_OBJECT + public: MythPainter(); /** MythPainter destructor. diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp index 01759fdc9d3..fbaa6015076 100644 --- a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp +++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp @@ -19,10 +19,20 @@ MythOpenGLPainter::MythOpenGLPainter(MythRenderOpenGL *Render, QWidget *Parent) if (!m_render) LOG(VB_GENERAL, LOG_ERR, "OpenGL painter has no render device"); + +#ifdef Q_OS_MACOS + m_display = MythDisplay::AcquireRelease(); + CurrentDPIChanged(m_widget->devicePixelRatioF()); + connect(m_display, &MythDisplay::CurrentDPIChanged, this, &MythOpenGLPainter::CurrentDPIChanged); +#endif } MythOpenGLPainter::~MythOpenGLPainter() { +#ifdef Q_OS_MACOS + MythDisplay::AcquireRelease(false); +#endif + if (!m_render) return; if (!m_render->IsReady()) @@ -85,6 +95,13 @@ void MythOpenGLPainter::ClearCache(void) m_imageToTextureMap.clear(); } +void MythOpenGLPainter::CurrentDPIChanged(qreal DPI) +{ + m_pixelRatio = DPI; + m_usingHighDPI = !qFuzzyCompare(m_pixelRatio, 1.0); + LOG(VB_GENERAL, LOG_INFO, QString("High DPI scaling %1").arg(m_usingHighDPI ? "enabled" : "disabled")); +} + void MythOpenGLPainter::Begin(QPaintDevice *Parent) { MythPainter::Begin(Parent); @@ -110,13 +127,17 @@ void MythOpenGLPainter::Begin(QPaintDevice *Parent) buf = m_render->CreateVBO(static_cast(MythRenderOpenGL::kVertexSize)); } + QSize currentsize = m_widget->size(); + // check if we need to adjust cache sizes - if (m_lastSize != m_widget->size()) + // NOTE - don't use the scaled size if using high DPI. Our images are at the lower + // resolution + if (m_lastSize != currentsize) { // This will scale the cache depending on the resolution in use static const int s_onehd = 1920 * 1080; static const int s_basesize = 64; - m_lastSize = m_widget->size(); + m_lastSize = currentsize; float hdscreens = (static_cast(m_lastSize.width() + 1) * m_lastSize.height()) / s_onehd; int cpu = qMax(static_cast(hdscreens * s_basesize), s_basesize); int gpu = cpu * 3 / 2; @@ -131,8 +152,11 @@ void MythOpenGLPainter::Begin(QPaintDevice *Parent) if (m_target || m_swapControl) { + // If we are master and using high DPI then scale the viewport + if (m_swapControl && m_usingHighDPI) + currentsize *= m_pixelRatio; m_render->BindFramebuffer(m_target); - m_render->SetViewPort(QRect(0, 0, m_widget->width(), m_widget->height())); + m_render->SetViewPort(QRect(0, 0, currentsize.width(), currentsize.height())); m_render->SetBackground(0, 0, 0, 0); m_render->ClearFramebuffer(); } @@ -222,12 +246,25 @@ MythGLTexture* MythOpenGLPainter::GetTextureFromCache(MythImage *Image) return texture; } +#ifdef Q_OS_MACOS +#define DEST dest +#else +#define DEST Dest +#endif + void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, const QRect &Source, int Alpha) { if (m_render) { - // Drawing an image multiple times with the same VBO will stall most GPUs as +#ifdef Q_OS_MACOS + QRect dest = QRect(static_cast(Dest.left() * m_pixelRatio), + static_cast(Dest.top() * m_pixelRatio), + static_cast(Dest.width() * m_pixelRatio), + static_cast(Dest.height() * m_pixelRatio)); +#endif + + // Drawing an image multiple times with the same VBO will stall most GPUs as // the VBO is re-mapped whilst still in use. Use a pooled VBO instead. MythGLTexture *texture = GetTextureFromCache(Image); if (texture && m_mappedTextures.contains(texture)) @@ -235,7 +272,7 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, QOpenGLBuffer *vbo = texture->m_vbo; texture->m_vbo = m_mappedBufferPool[m_mappedBufferPoolIdx]; texture->m_destination = QRect(); - m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha); + m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio); texture->m_destination = QRect(); texture->m_vbo = vbo; if (++m_mappedBufferPoolIdx >= MAX_BUFFER_POOL) @@ -243,17 +280,26 @@ void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, } else { - m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha); + m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio); m_mappedTextures.append(texture); } } } +/*! \brief Draw a rectangle + * + * If it is a simple rectangle, then use our own shaders for rendering (which + * saves texture memory but may not be as accurate as Qt rendering) otherwise + * fallback to Qt painting to a QImage, which is uploaded as a texture. + * + * \note If high DPI scaling is in use, just use Qt painting rather than + * handling all of the adjustments required for pen width etc etc. +*/ void MythOpenGLPainter::DrawRect(const QRect &Area, const QBrush &FillBrush, const QPen &LinePen, int Alpha) { if ((FillBrush.style() == Qt::SolidPattern || - FillBrush.style() == Qt::NoBrush) && m_render) + FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI) { m_render->DrawRect(m_target, Area, FillBrush, LinePen, Alpha); return; @@ -266,7 +312,7 @@ void MythOpenGLPainter::DrawRoundRect(const QRect &Area, int CornerRadius, const QPen &LinePen, int Alpha) { if ((FillBrush.style() == Qt::SolidPattern || - FillBrush.style() == Qt::NoBrush) && m_render) + FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI) { m_render->DrawRoundRect(m_target, Area, CornerRadius, FillBrush, LinePen, Alpha); diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.h b/mythtv/libs/libmythui/opengl/mythpainteropengl.h index e1cc830ca17..a633e107ca0 100644 --- a/mythtv/libs/libmythui/opengl/mythpainteropengl.h +++ b/mythtv/libs/libmythui/opengl/mythpainteropengl.h @@ -6,6 +6,7 @@ #include // MythTV +#include "mythdisplay.h" #include "mythpainter.h" #include "mythimage.h" @@ -22,6 +23,8 @@ class QOpenGLFramebufferObject; class MUI_PUBLIC MythOpenGLPainter : public MythPainter { + Q_OBJECT + public: explicit MythOpenGLPainter(MythRenderOpenGL *Render = nullptr, QWidget *Parent = nullptr); ~MythOpenGLPainter() override; @@ -46,6 +49,9 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter void PushTransformation(const UIEffects &Fx, QPointF Center = QPointF()) override; void PopTransformation(void) override; + public slots: + void CurrentDPIChanged(qreal DPI); + protected: void ClearCache(void); MythGLTexture* GetTextureFromCache(MythImage *Image); @@ -60,6 +66,9 @@ class MUI_PUBLIC MythOpenGLPainter : public MythPainter QOpenGLFramebufferObject* m_target { nullptr }; bool m_swapControl { true }; QSize m_lastSize { }; + qreal m_pixelRatio { 1.0 }; + MythDisplay* m_display { nullptr }; + bool m_usingHighDPI { false }; QMap m_imageToTextureMap; std::list m_ImageExpireList; diff --git a/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp b/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp index bd3891afd53..dbfb1c1c75c 100644 --- a/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp +++ b/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp @@ -802,7 +802,7 @@ void MythRenderOpenGL::ClearFramebuffer(void) void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, const QRect &Source, const QRect &Destination, - QOpenGLShaderProgram *Program, int Alpha) + QOpenGLShaderProgram *Program, int Alpha, qreal Scale) { makeCurrent(); @@ -825,7 +825,7 @@ void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObje QOpenGLBuffer* buffer = Texture->m_vbo; buffer->bind(); - if (UpdateTextureVertices(Texture, Source, Destination, 0)) + if (UpdateTextureVertices(Texture, Source, Destination, 0, Scale)) { if (m_extraFeaturesUsed & kGLBufferMap) { @@ -1260,7 +1260,7 @@ QStringList MythRenderOpenGL::GetDescription(void) } bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source, - const QRect &Destination, int Rotation) + const QRect &Destination, int Rotation, qreal Scale) { if (!Texture || (Texture && Texture->m_size.isEmpty())) return false; @@ -1299,8 +1299,8 @@ bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect data[4 + TEX_OFFSET] = data[6 + TEX_OFFSET]; data[5 + TEX_OFFSET] = data[1 + TEX_OFFSET]; - width = Texture->m_crop ? min(width, Destination.width()) : Destination.width(); - height = Texture->m_crop ? min(height, Destination.height()) : Destination.height(); + width = Texture->m_crop ? min(static_cast(width * Scale), Destination.width()) : Destination.width(); + height = Texture->m_crop ? min(static_cast(height * Scale), Destination.height()) : Destination.height(); data[2] = data[0] = Destination.left(); data[5] = data[1] = Destination.top(); diff --git a/mythtv/libs/libmythui/opengl/mythrenderopengl.h b/mythtv/libs/libmythui/opengl/mythrenderopengl.h index 199f0d642be..2ccb9a60d5b 100644 --- a/mythtv/libs/libmythui/opengl/mythrenderopengl.h +++ b/mythtv/libs/libmythui/opengl/mythrenderopengl.h @@ -143,7 +143,7 @@ class MUI_PUBLIC MythRenderOpenGL : public QOpenGLContext, public QOpenGLFunctio void DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, const QRect &Source, const QRect &Destination, - QOpenGLShaderProgram *Program, int Alpha = 255); + QOpenGLShaderProgram *Program, int Alpha = 255, qreal Scale = 1.0); void DrawBitmap(MythGLTexture **Textures, uint TextureCount, QOpenGLFramebufferObject *Target, const QRect &Source, const QRect &Destination, @@ -171,7 +171,7 @@ class MUI_PUBLIC MythRenderOpenGL : public QOpenGLContext, public QOpenGLFunctio void SetMatrixView(void); void DeleteFramebuffers(void); static bool UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source, - const QRect &Destination, int Rotation); + const QRect &Destination, int Rotation, qreal Scale = 1.0); GLfloat* GetCachedVertices(GLuint Type, const QRect &Area); void ExpireVertices(int Max = 0); void GetCachedVBO(GLuint Type, const QRect &Area);