diff --git a/mythtv/libs/libmythtv/mythvideoout.cpp b/mythtv/libs/libmythtv/mythvideoout.cpp
index 8eb608b53e..79b408aaf0 100644
a
|
b
|
void MythVideoOutput::InitDisplayMeasurements(void) |
1019 | 1019 | .arg(displayaspect).arg(source)); |
1020 | 1020 | |
1021 | 1021 | // Get the window and screen resolutions |
1022 | | QSize window = m_window.GetWindowRect().size(); |
| 1022 | QSize window = m_window.GetRawWindowRect().size(); |
1023 | 1023 | QSize screen = m_display->GetResolution(); |
1024 | 1024 | |
1025 | 1025 | // 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 cba4327174..8aca6995cb 100644
a
|
b
|
|
38 | 38 | |
39 | 39 | #define LOC QString("VideoWin: ") |
40 | 40 | |
| 41 | #define SCALED_RECT(SRC, SCALE) QRect{ static_cast<int>(SRC.left() * SCALE), \ |
| 42 | static_cast<int>(SRC.top() * SCALE), \ |
| 43 | static_cast<int>(SRC.width() * SCALE), \ |
| 44 | static_cast<int>(SRC.height() * SCALE) } |
| 45 | |
41 | 46 | static float fix_aspect(float raw); |
42 | 47 | static float snap(float value, float snapto, float diff); |
43 | 48 | |
… |
… |
void VideoOutWindow::ScreenChanged(QScreen */*screen*/) |
62 | 67 | MoveResize(); |
63 | 68 | } |
64 | 69 | |
| 70 | void VideoOutWindow::PhysicalDPIChanged(qreal /*DPI*/) |
| 71 | { |
| 72 | // PopulateGeometry will update m_devicePixelRatio |
| 73 | PopulateGeometry(); |
| 74 | m_windowRect = m_displayVisibleRect = SCALED_RECT(m_rawWindowRect, m_devicePixelRatio); |
| 75 | MoveResize(); |
| 76 | } |
| 77 | |
65 | 78 | void VideoOutWindow::PopulateGeometry(void) |
66 | 79 | { |
67 | 80 | if (!m_display) |
… |
… |
void VideoOutWindow::PopulateGeometry(void) |
71 | 84 | if (!screen) |
72 | 85 | return; |
73 | 86 | |
| 87 | #ifdef Q_OS_MACOS |
| 88 | m_devicePixelRatio = screen->devicePixelRatio(); |
| 89 | #endif |
| 90 | |
74 | 91 | if (MythDisplay::SpanAllScreens() && MythDisplay::GetScreenCount() > 1) |
75 | 92 | { |
76 | 93 | m_screenGeometry = screen->virtualGeometry(); |
… |
… |
bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim, |
415 | 432 | { |
416 | 433 | m_display = Display; |
417 | 434 | connect(m_display, &MythDisplay::CurrentScreenChanged, this, &VideoOutWindow::ScreenChanged); |
| 435 | #ifdef Q_OS_MACOS |
| 436 | connect(m_display, &MythDisplay::PhysicalDPIChanged, this, &VideoOutWindow::PhysicalDPIChanged); |
| 437 | #endif |
418 | 438 | } |
419 | 439 | |
420 | 440 | if (m_display) |
… |
… |
bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim, |
428 | 448 | |
429 | 449 | // N.B. we are always confined to the window size so use that for the initial |
430 | 450 | // displayVisibleRect |
431 | | m_windowRect = m_displayVisibleRect = WindowRect; |
| 451 | m_rawWindowRect = WindowRect; |
| 452 | m_windowRect = m_displayVisibleRect = SCALED_RECT(WindowRect, m_devicePixelRatio); |
432 | 453 | |
433 | 454 | int pbp_width = m_displayVisibleRect.width() / 2; |
434 | 455 | if (m_pipState == kPBPLeft || m_pipState == kPBPRight) |
… |
… |
void VideoOutWindow::SetDisplayAspect(float DisplayAspect) |
612 | 633 | |
613 | 634 | void VideoOutWindow::SetWindowSize(QSize Size) |
614 | 635 | { |
615 | | if (Size != m_windowRect.size()) |
| 636 | if (Size != m_rawWindowRect.size()) |
616 | 637 | { |
617 | | QRect rect(m_windowRect.topLeft(), Size); |
| 638 | QRect rect(m_rawWindowRect.topLeft(), Size); |
618 | 639 | LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New window rect: %1x%2+%3+%4") |
619 | 640 | .arg(rect.width()).arg(rect.height()).arg(rect.left()).arg(rect.top())); |
620 | | m_windowRect = m_displayVisibleRect = rect; |
| 641 | m_rawWindowRect = rect; |
| 642 | m_windowRect = m_displayVisibleRect = SCALED_RECT(rect, m_devicePixelRatio); |
621 | 643 | MoveResize(); |
622 | 644 | } |
623 | 645 | } |
diff --git a/mythtv/libs/libmythtv/videooutwindow.h b/mythtv/libs/libmythtv/videooutwindow.h
index b5e3ab579e..50ee9428cb 100644
a
|
b
|
class VideoOutWindow : public QObject |
45 | 45 | |
46 | 46 | public slots: |
47 | 47 | void ScreenChanged (QScreen *screen); |
| 48 | void PhysicalDPIChanged (qreal /*DPI*/); |
48 | 49 | |
49 | 50 | // Sets |
50 | 51 | void InputChanged (const QSize &VideoDim, const QSize &VideoDispDim, float Aspect); |
… |
… |
class VideoOutWindow : public QObject |
74 | 75 | float GetOverridenVideoAspect(void) const { return m_videoAspectOverride;} |
75 | 76 | QRect GetDisplayVisibleRect(void) const { return m_displayVisibleRect; } |
76 | 77 | QRect GetWindowRect(void) const { return m_windowRect; } |
| 78 | QRect GetRawWindowRect(void) const { return m_rawWindowRect; } |
77 | 79 | QRect GetScreenGeometry(void) const { return m_screenGeometry; } |
78 | 80 | QRect GetVideoRect(void) const { return m_videoRect; } |
79 | 81 | QRect GetDisplayVideoRect(void) const { return m_displayVideoRect; } |
… |
… |
class VideoOutWindow : public QObject |
113 | 115 | bool m_dbScalingAllowed {true}; ///< disable this to prevent overscan/underscan |
114 | 116 | bool m_dbUseGUISize {false}; ///< Use the gui size for video window |
115 | 117 | QRect m_screenGeometry {0,0,1024,768}; ///< Full screen geometry |
| 118 | qreal m_devicePixelRatio {1.0}; |
116 | 119 | |
117 | 120 | // Manual Zoom |
118 | 121 | float m_manualVertScale {1.0F}; ///< Manually applied vertical scaling. |
… |
… |
class VideoOutWindow : public QObject |
145 | 148 | QRect m_displayVisibleRect {0,0,0,0}; |
146 | 149 | /// Rectangle describing QWidget bounds. |
147 | 150 | QRect m_windowRect {0,0,0,0}; |
| 151 | /// Rectangle describing QWidget bounds - not adjusted for high DPI scaling (macos) |
| 152 | QRect m_rawWindowRect {0,0,0,0}; |
148 | 153 | /// Used to save the display_visible_rect for |
149 | 154 | /// restoration after video embedding ends. |
150 | 155 | QRect m_tmpDisplayVisibleRect {0,0,0,0}; |
diff --git a/mythtv/libs/libmythui/mythpainter.h b/mythtv/libs/libmythui/mythpainter.h
index 5612456997..d7367fb877 100644
a
|
b
|
class UIEffects; |
29 | 29 | using LayoutVector = QVector<QTextLayout *>; |
30 | 30 | using FormatVector = QVector<QTextLayout::FormatRange>; |
31 | 31 | |
32 | | class MUI_PUBLIC MythPainter |
| 32 | class MUI_PUBLIC MythPainter : public QObject |
33 | 33 | { |
| 34 | Q_OBJECT |
| 35 | |
34 | 36 | public: |
35 | 37 | MythPainter(); |
36 | 38 | /** MythPainter destructor. |
diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp b/mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
index 01759fdc9d..fbaa601507 100644
a
|
b
|
MythOpenGLPainter::MythOpenGLPainter(MythRenderOpenGL *Render, QWidget *Parent) |
19 | 19 | |
20 | 20 | if (!m_render) |
21 | 21 | LOG(VB_GENERAL, LOG_ERR, "OpenGL painter has no render device"); |
| 22 | |
| 23 | #ifdef Q_OS_MACOS |
| 24 | m_display = MythDisplay::AcquireRelease(); |
| 25 | CurrentDPIChanged(m_widget->devicePixelRatioF()); |
| 26 | connect(m_display, &MythDisplay::CurrentDPIChanged, this, &MythOpenGLPainter::CurrentDPIChanged); |
| 27 | #endif |
22 | 28 | } |
23 | 29 | |
24 | 30 | MythOpenGLPainter::~MythOpenGLPainter() |
25 | 31 | { |
| 32 | #ifdef Q_OS_MACOS |
| 33 | MythDisplay::AcquireRelease(false); |
| 34 | #endif |
| 35 | |
26 | 36 | if (!m_render) |
27 | 37 | return; |
28 | 38 | if (!m_render->IsReady()) |
… |
… |
void MythOpenGLPainter::ClearCache(void) |
85 | 95 | m_imageToTextureMap.clear(); |
86 | 96 | } |
87 | 97 | |
| 98 | void MythOpenGLPainter::CurrentDPIChanged(qreal DPI) |
| 99 | { |
| 100 | m_pixelRatio = DPI; |
| 101 | m_usingHighDPI = !qFuzzyCompare(m_pixelRatio, 1.0); |
| 102 | LOG(VB_GENERAL, LOG_INFO, QString("High DPI scaling %1").arg(m_usingHighDPI ? "enabled" : "disabled")); |
| 103 | } |
| 104 | |
88 | 105 | void MythOpenGLPainter::Begin(QPaintDevice *Parent) |
89 | 106 | { |
90 | 107 | MythPainter::Begin(Parent); |
… |
… |
void MythOpenGLPainter::Begin(QPaintDevice *Parent) |
110 | 127 | buf = m_render->CreateVBO(static_cast<int>(MythRenderOpenGL::kVertexSize)); |
111 | 128 | } |
112 | 129 | |
| 130 | QSize currentsize = m_widget->size(); |
| 131 | |
113 | 132 | // check if we need to adjust cache sizes |
114 | | if (m_lastSize != m_widget->size()) |
| 133 | // NOTE - don't use the scaled size if using high DPI. Our images are at the lower |
| 134 | // resolution |
| 135 | if (m_lastSize != currentsize) |
115 | 136 | { |
116 | 137 | // This will scale the cache depending on the resolution in use |
117 | 138 | static const int s_onehd = 1920 * 1080; |
118 | 139 | static const int s_basesize = 64; |
119 | | m_lastSize = m_widget->size(); |
| 140 | m_lastSize = currentsize; |
120 | 141 | float hdscreens = (static_cast<float>(m_lastSize.width() + 1) * m_lastSize.height()) / s_onehd; |
121 | 142 | int cpu = qMax(static_cast<int>(hdscreens * s_basesize), s_basesize); |
122 | 143 | int gpu = cpu * 3 / 2; |
… |
… |
void MythOpenGLPainter::Begin(QPaintDevice *Parent) |
131 | 152 | |
132 | 153 | if (m_target || m_swapControl) |
133 | 154 | { |
| 155 | // If we are master and using high DPI then scale the viewport |
| 156 | if (m_swapControl && m_usingHighDPI) |
| 157 | currentsize *= m_pixelRatio; |
134 | 158 | m_render->BindFramebuffer(m_target); |
135 | | m_render->SetViewPort(QRect(0, 0, m_widget->width(), m_widget->height())); |
| 159 | m_render->SetViewPort(QRect(0, 0, currentsize.width(), currentsize.height())); |
136 | 160 | m_render->SetBackground(0, 0, 0, 0); |
137 | 161 | m_render->ClearFramebuffer(); |
138 | 162 | } |
… |
… |
MythGLTexture* MythOpenGLPainter::GetTextureFromCache(MythImage *Image) |
222 | 246 | return texture; |
223 | 247 | } |
224 | 248 | |
| 249 | #ifdef Q_OS_MACOS |
| 250 | #define DEST dest |
| 251 | #else |
| 252 | #define DEST Dest |
| 253 | #endif |
| 254 | |
225 | 255 | void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, |
226 | 256 | const QRect &Source, int Alpha) |
227 | 257 | { |
228 | 258 | if (m_render) |
229 | 259 | { |
230 | | // Drawing an image multiple times with the same VBO will stall most GPUs as |
| 260 | #ifdef Q_OS_MACOS |
| 261 | QRect dest = QRect(static_cast<int>(Dest.left() * m_pixelRatio), |
| 262 | static_cast<int>(Dest.top() * m_pixelRatio), |
| 263 | static_cast<int>(Dest.width() * m_pixelRatio), |
| 264 | static_cast<int>(Dest.height() * m_pixelRatio)); |
| 265 | #endif |
| 266 | |
| 267 | // Drawing an image multiple times with the same VBO will stall most GPUs as |
231 | 268 | // the VBO is re-mapped whilst still in use. Use a pooled VBO instead. |
232 | 269 | MythGLTexture *texture = GetTextureFromCache(Image); |
233 | 270 | if (texture && m_mappedTextures.contains(texture)) |
… |
… |
void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, |
235 | 272 | QOpenGLBuffer *vbo = texture->m_vbo; |
236 | 273 | texture->m_vbo = m_mappedBufferPool[m_mappedBufferPoolIdx]; |
237 | 274 | texture->m_destination = QRect(); |
238 | | m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha); |
| 275 | m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio); |
239 | 276 | texture->m_destination = QRect(); |
240 | 277 | texture->m_vbo = vbo; |
241 | 278 | if (++m_mappedBufferPoolIdx >= MAX_BUFFER_POOL) |
… |
… |
void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, |
243 | 280 | } |
244 | 281 | else |
245 | 282 | { |
246 | | m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha); |
| 283 | m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio); |
247 | 284 | m_mappedTextures.append(texture); |
248 | 285 | } |
249 | 286 | } |
250 | 287 | } |
251 | 288 | |
| 289 | /*! \brief Draw a rectangle |
| 290 | * |
| 291 | * If it is a simple rectangle, then use our own shaders for rendering (which |
| 292 | * saves texture memory but may not be as accurate as Qt rendering) otherwise |
| 293 | * fallback to Qt painting to a QImage, which is uploaded as a texture. |
| 294 | * |
| 295 | * \note If high DPI scaling is in use, just use Qt painting rather than |
| 296 | * handling all of the adjustments required for pen width etc etc. |
| 297 | */ |
252 | 298 | void MythOpenGLPainter::DrawRect(const QRect &Area, const QBrush &FillBrush, |
253 | 299 | const QPen &LinePen, int Alpha) |
254 | 300 | { |
255 | 301 | if ((FillBrush.style() == Qt::SolidPattern || |
256 | | FillBrush.style() == Qt::NoBrush) && m_render) |
| 302 | FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI) |
257 | 303 | { |
258 | 304 | m_render->DrawRect(m_target, Area, FillBrush, LinePen, Alpha); |
259 | 305 | return; |
… |
… |
void MythOpenGLPainter::DrawRoundRect(const QRect &Area, int CornerRadius, |
266 | 312 | const QPen &LinePen, int Alpha) |
267 | 313 | { |
268 | 314 | if ((FillBrush.style() == Qt::SolidPattern || |
269 | | FillBrush.style() == Qt::NoBrush) && m_render) |
| 315 | FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI) |
270 | 316 | { |
271 | 317 | m_render->DrawRoundRect(m_target, Area, CornerRadius, FillBrush, |
272 | 318 | LinePen, Alpha); |
diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.h b/mythtv/libs/libmythui/opengl/mythpainteropengl.h
index e1cc830ca1..a633e107ca 100644
a
|
b
|
|
6 | 6 | #include <QQueue> |
7 | 7 | |
8 | 8 | // MythTV |
| 9 | #include "mythdisplay.h" |
9 | 10 | #include "mythpainter.h" |
10 | 11 | #include "mythimage.h" |
11 | 12 | |
… |
… |
class QOpenGLFramebufferObject; |
22 | 23 | |
23 | 24 | class MUI_PUBLIC MythOpenGLPainter : public MythPainter |
24 | 25 | { |
| 26 | Q_OBJECT |
| 27 | |
25 | 28 | public: |
26 | 29 | explicit MythOpenGLPainter(MythRenderOpenGL *Render = nullptr, QWidget *Parent = nullptr); |
27 | 30 | ~MythOpenGLPainter() override; |
… |
… |
class MUI_PUBLIC MythOpenGLPainter : public MythPainter |
46 | 49 | void PushTransformation(const UIEffects &Fx, QPointF Center = QPointF()) override; |
47 | 50 | void PopTransformation(void) override; |
48 | 51 | |
| 52 | public slots: |
| 53 | void CurrentDPIChanged(qreal DPI); |
| 54 | |
49 | 55 | protected: |
50 | 56 | void ClearCache(void); |
51 | 57 | MythGLTexture* GetTextureFromCache(MythImage *Image); |
… |
… |
class MUI_PUBLIC MythOpenGLPainter : public MythPainter |
60 | 66 | QOpenGLFramebufferObject* m_target { nullptr }; |
61 | 67 | bool m_swapControl { true }; |
62 | 68 | QSize m_lastSize { }; |
| 69 | qreal m_pixelRatio { 1.0 }; |
| 70 | MythDisplay* m_display { nullptr }; |
| 71 | bool m_usingHighDPI { false }; |
63 | 72 | |
64 | 73 | QMap<MythImage *, MythGLTexture*> m_imageToTextureMap; |
65 | 74 | std::list<MythImage *> m_ImageExpireList; |
diff --git a/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp b/mythtv/libs/libmythui/opengl/mythrenderopengl.cpp
index bd3891afd5..dbfb1c1c75 100644
a
|
b
|
void MythRenderOpenGL::ClearFramebuffer(void) |
802 | 802 | |
803 | 803 | void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, |
804 | 804 | const QRect &Source, const QRect &Destination, |
805 | | QOpenGLShaderProgram *Program, int Alpha) |
| 805 | QOpenGLShaderProgram *Program, int Alpha, qreal Scale) |
806 | 806 | { |
807 | 807 | makeCurrent(); |
808 | 808 | |
… |
… |
void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObje |
825 | 825 | |
826 | 826 | QOpenGLBuffer* buffer = Texture->m_vbo; |
827 | 827 | buffer->bind(); |
828 | | if (UpdateTextureVertices(Texture, Source, Destination, 0)) |
| 828 | if (UpdateTextureVertices(Texture, Source, Destination, 0, Scale)) |
829 | 829 | { |
830 | 830 | if (m_extraFeaturesUsed & kGLBufferMap) |
831 | 831 | { |
… |
… |
QStringList MythRenderOpenGL::GetDescription(void) |
1260 | 1260 | } |
1261 | 1261 | |
1262 | 1262 | bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source, |
1263 | | const QRect &Destination, int Rotation) |
| 1263 | const QRect &Destination, int Rotation, qreal Scale) |
1264 | 1264 | { |
1265 | 1265 | if (!Texture || (Texture && Texture->m_size.isEmpty())) |
1266 | 1266 | return false; |
… |
… |
bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect |
1299 | 1299 | data[4 + TEX_OFFSET] = data[6 + TEX_OFFSET]; |
1300 | 1300 | data[5 + TEX_OFFSET] = data[1 + TEX_OFFSET]; |
1301 | 1301 | |
1302 | | width = Texture->m_crop ? min(width, Destination.width()) : Destination.width(); |
1303 | | height = Texture->m_crop ? min(height, Destination.height()) : Destination.height(); |
| 1302 | width = Texture->m_crop ? min(static_cast<int>(width * Scale), Destination.width()) : Destination.width(); |
| 1303 | height = Texture->m_crop ? min(static_cast<int>(height * Scale), Destination.height()) : Destination.height(); |
1304 | 1304 | |
1305 | 1305 | data[2] = data[0] = Destination.left(); |
1306 | 1306 | data[5] = data[1] = Destination.top(); |
diff --git a/mythtv/libs/libmythui/opengl/mythrenderopengl.h b/mythtv/libs/libmythui/opengl/mythrenderopengl.h
index 199f0d642b..2ccb9a60d5 100644
a
|
b
|
class MUI_PUBLIC MythRenderOpenGL : public QOpenGLContext, public QOpenGLFunctio |
143 | 143 | |
144 | 144 | void DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, |
145 | 145 | const QRect &Source, const QRect &Destination, |
146 | | QOpenGLShaderProgram *Program, int Alpha = 255); |
| 146 | QOpenGLShaderProgram *Program, int Alpha = 255, qreal Scale = 1.0); |
147 | 147 | void DrawBitmap(MythGLTexture **Textures, uint TextureCount, |
148 | 148 | QOpenGLFramebufferObject *Target, |
149 | 149 | const QRect &Source, const QRect &Destination, |
… |
… |
class MUI_PUBLIC MythRenderOpenGL : public QOpenGLContext, public QOpenGLFunctio |
171 | 171 | void SetMatrixView(void); |
172 | 172 | void DeleteFramebuffers(void); |
173 | 173 | static bool UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source, |
174 | | const QRect &Destination, int Rotation); |
| 174 | const QRect &Destination, int Rotation, qreal Scale = 1.0); |
175 | 175 | GLfloat* GetCachedVertices(GLuint Type, const QRect &Area); |
176 | 176 | void ExpireVertices(int Max = 0); |
177 | 177 | void GetCachedVBO(GLuint Type, const QRect &Area); |