diff --git a/mythtv/libs/libmythtv/mythvideoout.cpp b/mythtv/libs/libmythtv/mythvideoout.cpp
index ee4b759d81..888b283cfc 100644
a
|
b
|
void MythVideoOutput::InitDisplayMeasurements(void) |
1020 | 1020 | .arg(displayaspect).arg(source)); |
1021 | 1021 | |
1022 | 1022 | // Get the window and screen resolutions |
1023 | | QSize window = m_window.GetWindowRect().size(); |
| 1023 | QSize window = m_window.GetRawWindowRect().size(); |
1024 | 1024 | QSize screen = m_display->GetResolution(); |
1025 | 1025 | |
1026 | 1026 | // 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 af8af15c31..9c7326f11c 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*/) |
63 | 68 | MoveResize(); |
64 | 69 | } |
65 | 70 | |
| 71 | void VideoOutWindow::PhysicalDPIChanged(qreal /*DPI*/) |
| 72 | { |
| 73 | // PopulateGeometry will update m_devicePixelRatio |
| 74 | PopulateGeometry(); |
| 75 | m_windowRect = m_displayVisibleRect = SCALED_RECT(m_rawWindowRect, m_devicePixelRatio); |
| 76 | MoveResize(); |
| 77 | } |
| 78 | |
66 | 79 | void VideoOutWindow::PopulateGeometry(void) |
67 | 80 | { |
68 | 81 | if (!m_display) |
… |
… |
void VideoOutWindow::PopulateGeometry(void) |
72 | 85 | if (!screen) |
73 | 86 | return; |
74 | 87 | |
| 88 | #ifdef Q_OS_MACOS |
| 89 | m_devicePixelRatio = screen->devicePixelRatio(); |
| 90 | #endif |
| 91 | |
75 | 92 | if (MythDisplay::SpanAllScreens() && MythDisplay::GetScreenCount() > 1) |
76 | 93 | { |
77 | 94 | m_screenGeometry = screen->virtualGeometry(); |
… |
… |
bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim, |
416 | 433 | { |
417 | 434 | m_display = Display; |
418 | 435 | connect(m_display, &MythDisplay::CurrentScreenChanged, this, &VideoOutWindow::ScreenChanged); |
| 436 | #ifdef Q_OS_MACOS |
| 437 | connect(m_display, &MythDisplay::PhysicalDPIChanged, this, &VideoOutWindow::PhysicalDPIChanged); |
| 438 | #endif |
419 | 439 | } |
420 | 440 | |
421 | 441 | if (m_display) |
… |
… |
bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim, |
429 | 449 | |
430 | 450 | // N.B. we are always confined to the window size so use that for the initial |
431 | 451 | // displayVisibleRect |
432 | | m_windowRect = m_displayVisibleRect = WindowRect; |
| 452 | m_rawWindowRect = WindowRect; |
| 453 | m_windowRect = m_displayVisibleRect = SCALED_RECT(WindowRect, m_devicePixelRatio); |
433 | 454 | |
434 | 455 | int pbp_width = m_displayVisibleRect.width() / 2; |
435 | 456 | if (m_pipState == kPBPLeft || m_pipState == kPBPRight) |
… |
… |
void VideoOutWindow::SetDisplayAspect(float DisplayAspect) |
613 | 634 | |
614 | 635 | void VideoOutWindow::SetWindowSize(QSize Size) |
615 | 636 | { |
616 | | if (Size != m_windowRect.size()) |
| 637 | if (Size != m_rawWindowRect.size()) |
617 | 638 | { |
618 | | QRect rect(m_windowRect.topLeft(), Size); |
| 639 | QRect rect(m_rawWindowRect.topLeft(), Size); |
619 | 640 | LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New window rect: %1x%2+%3+%4") |
620 | 641 | .arg(rect.width()).arg(rect.height()).arg(rect.left()).arg(rect.top())); |
621 | | m_windowRect = m_displayVisibleRect = rect; |
| 642 | m_rawWindowRect = rect; |
| 643 | m_windowRect = m_displayVisibleRect = SCALED_RECT(rect, m_devicePixelRatio); |
622 | 644 | MoveResize(); |
623 | 645 | } |
624 | 646 | } |
diff --git a/mythtv/libs/libmythtv/videooutwindow.h b/mythtv/libs/libmythtv/videooutwindow.h
index 9480045c92..cce077b5fd 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 |
115 | 117 | bool m_dbScalingAllowed {true}; ///< disable this to prevent overscan/underscan |
116 | 118 | bool m_dbUseGUISize {false}; ///< Use the gui size for video window |
117 | 119 | QRect m_screenGeometry {0,0,1024,768}; ///< Full screen geometry |
| 120 | qreal m_devicePixelRatio {1.0}; |
118 | 121 | |
119 | 122 | // Manual Zoom |
120 | 123 | float m_manualVertScale {1.0F}; ///< Manually applied vertical scaling. |
… |
… |
class VideoOutWindow : public QObject |
147 | 150 | QRect m_displayVisibleRect {0,0,0,0}; |
148 | 151 | /// Rectangle describing QWidget bounds. |
149 | 152 | QRect m_windowRect {0,0,0,0}; |
| 153 | /// Rectangle describing QWidget bounds - not adjusted for high DPI scaling (macos) |
| 154 | QRect m_rawWindowRect {0,0,0,0}; |
150 | 155 | /// Used to save the display_visible_rect for |
151 | 156 | /// restoration after video embedding ends. |
152 | 157 | QRect m_tmpDisplayVisibleRect {0,0,0,0}; |
diff --git a/mythtv/libs/libmythui/mythpainter.h b/mythtv/libs/libmythui/mythpainter.h
index b6b054a813..67175a1ed5 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 8fec14d3c2..abbb7685f0 100644
a
|
b
|
MythOpenGLPainter::MythOpenGLPainter(MythRenderOpenGL *Render, QWidget *Parent) |
20 | 20 | |
21 | 21 | if (!m_render) |
22 | 22 | LOG(VB_GENERAL, LOG_ERR, "OpenGL painter has no render device"); |
| 23 | |
| 24 | #ifdef Q_OS_MACOS |
| 25 | m_display = MythDisplay::AcquireRelease(); |
| 26 | CurrentDPIChanged(m_parent->devicePixelRatioF()); |
| 27 | connect(m_display, &MythDisplay::CurrentDPIChanged, this, &MythOpenGLPainter::CurrentDPIChanged); |
| 28 | #endif |
23 | 29 | } |
24 | 30 | |
25 | 31 | MythOpenGLPainter::~MythOpenGLPainter() |
26 | 32 | { |
| 33 | #ifdef Q_OS_MACOS |
| 34 | MythDisplay::AcquireRelease(false); |
| 35 | #endif |
| 36 | |
27 | 37 | if (!m_render) |
28 | 38 | return; |
29 | 39 | if (!m_render->IsReady()) |
… |
… |
void MythOpenGLPainter::ClearCache(void) |
84 | 94 | m_imageToTextureMap.clear(); |
85 | 95 | } |
86 | 96 | |
| 97 | void MythOpenGLPainter::CurrentDPIChanged(qreal DPI) |
| 98 | { |
| 99 | m_pixelRatio = DPI; |
| 100 | m_usingHighDPI = !qFuzzyCompare(m_pixelRatio, 1.0); |
| 101 | LOG(VB_GENERAL, LOG_INFO, QString("High DPI scaling %1").arg(m_usingHighDPI ? "enabled" : "disabled")); |
| 102 | } |
| 103 | |
87 | 104 | void MythOpenGLPainter::Begin(QPaintDevice *Parent) |
88 | 105 | { |
89 | 106 | MythPainter::Begin(Parent); |
… |
… |
void MythOpenGLPainter::Begin(QPaintDevice *Parent) |
109 | 126 | buf = m_render->CreateVBO(static_cast<int>(MythRenderOpenGL::kVertexSize)); |
110 | 127 | } |
111 | 128 | |
| 129 | QSize currentsize = m_parent->size(); |
| 130 | |
112 | 131 | // check if we need to adjust cache sizes |
113 | | if (m_lastSize != m_parent->size()) |
| 132 | // NOTE - don't use the scaled size if using high DPI. Our images are at the lower |
| 133 | // resolution |
| 134 | if (m_lastSize != currentsize) |
114 | 135 | { |
115 | 136 | // This will scale the cache depending on the resolution in use |
116 | 137 | static const int s_onehd = 1920 * 1080; |
117 | 138 | static const int s_basesize = 64; |
118 | | m_lastSize = m_parent->size(); |
| 139 | m_lastSize = currentsize; |
119 | 140 | float hdscreens = (static_cast<float>(m_lastSize.width() + 1) * m_lastSize.height()) / s_onehd; |
120 | 141 | int cpu = qMax(static_cast<int>(hdscreens * s_basesize), s_basesize); |
121 | 142 | int gpu = cpu * 3 / 2; |
… |
… |
void MythOpenGLPainter::Begin(QPaintDevice *Parent) |
130 | 151 | |
131 | 152 | if (m_target || m_swapControl) |
132 | 153 | { |
| 154 | // If we are master and using high DPI then scale the viewport |
| 155 | if (m_swapControl && m_usingHighDPI) |
| 156 | currentsize *= m_pixelRatio; |
133 | 157 | m_render->BindFramebuffer(m_target); |
134 | | m_render->SetViewPort(QRect(0, 0, m_parent->width(), m_parent->height())); |
| 158 | m_render->SetViewPort(QRect(0, 0, currentsize.width(), currentsize.height())); |
135 | 159 | m_render->SetBackground(0, 0, 0, 0); |
136 | 160 | m_render->ClearFramebuffer(); |
137 | 161 | } |
… |
… |
MythGLTexture* MythOpenGLPainter::GetTextureFromCache(MythImage *Image) |
221 | 245 | return texture; |
222 | 246 | } |
223 | 247 | |
| 248 | #ifdef Q_OS_MACOS |
| 249 | #define DEST dest |
| 250 | #else |
| 251 | #define DEST Dest |
| 252 | #endif |
| 253 | |
224 | 254 | void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, |
225 | 255 | const QRect &Source, int Alpha) |
226 | 256 | { |
227 | 257 | if (m_render) |
228 | 258 | { |
229 | | // Drawing an image multiple times with the same VBO will stall most GPUs as |
| 259 | #ifdef Q_OS_MACOS |
| 260 | QRect dest = QRect(static_cast<int>(Dest.left() * m_pixelRatio), |
| 261 | static_cast<int>(Dest.top() * m_pixelRatio), |
| 262 | static_cast<int>(Dest.width() * m_pixelRatio), |
| 263 | static_cast<int>(Dest.height() * m_pixelRatio)); |
| 264 | #endif |
| 265 | |
| 266 | // Drawing an image multiple times with the same VBO will stall most GPUs as |
230 | 267 | // the VBO is re-mapped whilst still in use. Use a pooled VBO instead. |
231 | 268 | MythGLTexture *texture = GetTextureFromCache(Image); |
232 | 269 | if (texture && m_mappedTextures.contains(texture)) |
… |
… |
void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, |
234 | 271 | QOpenGLBuffer *vbo = texture->m_vbo; |
235 | 272 | texture->m_vbo = m_mappedBufferPool[m_mappedBufferPoolIdx]; |
236 | 273 | texture->m_destination = QRect(); |
237 | | m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha); |
| 274 | m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio); |
238 | 275 | texture->m_destination = QRect(); |
239 | 276 | texture->m_vbo = vbo; |
240 | 277 | if (++m_mappedBufferPoolIdx >= MAX_BUFFER_POOL) |
… |
… |
void MythOpenGLPainter::DrawImage(const QRect &Dest, MythImage *Image, |
242 | 279 | } |
243 | 280 | else |
244 | 281 | { |
245 | | m_render->DrawBitmap(texture, m_target, Source, Dest, nullptr, Alpha); |
| 282 | m_render->DrawBitmap(texture, m_target, Source, DEST, nullptr, Alpha, m_pixelRatio); |
246 | 283 | m_mappedTextures.append(texture); |
247 | 284 | } |
248 | 285 | } |
249 | 286 | } |
250 | 287 | |
| 288 | /*! \brief Draw a rectangle |
| 289 | * |
| 290 | * If it is a simple rectangle, then use our own shaders for rendering (which |
| 291 | * saves texture memory but may not be as accurate as Qt rendering) otherwise |
| 292 | * fallback to Qt painting to a QImage, which is uploaded as a texture. |
| 293 | * |
| 294 | * \note If high DPI scaling is in use, just use Qt painting rather than |
| 295 | * handling all of the adjustments required for pen width etc etc. |
| 296 | */ |
251 | 297 | void MythOpenGLPainter::DrawRect(const QRect &Area, const QBrush &FillBrush, |
252 | 298 | const QPen &LinePen, int Alpha) |
253 | 299 | { |
254 | 300 | if ((FillBrush.style() == Qt::SolidPattern || |
255 | | FillBrush.style() == Qt::NoBrush) && m_render) |
| 301 | FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI) |
256 | 302 | { |
257 | 303 | m_render->DrawRect(m_target, Area, FillBrush, LinePen, Alpha); |
258 | 304 | return; |
… |
… |
void MythOpenGLPainter::DrawRoundRect(const QRect &Area, int CornerRadius, |
265 | 311 | const QPen &LinePen, int Alpha) |
266 | 312 | { |
267 | 313 | if ((FillBrush.style() == Qt::SolidPattern || |
268 | | FillBrush.style() == Qt::NoBrush) && m_render) |
| 314 | FillBrush.style() == Qt::NoBrush) && m_render && !m_usingHighDPI) |
269 | 315 | { |
270 | 316 | m_render->DrawRoundRect(m_target, Area, CornerRadius, FillBrush, |
271 | 317 | LinePen, Alpha); |
diff --git a/mythtv/libs/libmythui/opengl/mythpainteropengl.h b/mythtv/libs/libmythui/opengl/mythpainteropengl.h
index 097577231f..540f7db79d 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 e34320f3dc..684740c584 100644
a
|
b
|
void MythRenderOpenGL::ClearFramebuffer(void) |
804 | 804 | |
805 | 805 | void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, |
806 | 806 | const QRect &Source, const QRect &Destination, |
807 | | QOpenGLShaderProgram *Program, int Alpha) |
| 807 | QOpenGLShaderProgram *Program, int Alpha, qreal Scale) |
808 | 808 | { |
809 | 809 | makeCurrent(); |
810 | 810 | |
… |
… |
void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObje |
827 | 827 | |
828 | 828 | QOpenGLBuffer* buffer = Texture->m_vbo; |
829 | 829 | buffer->bind(); |
830 | | if (UpdateTextureVertices(Texture, Source, Destination, 0)) |
| 830 | if (UpdateTextureVertices(Texture, Source, Destination, 0, Scale)) |
831 | 831 | { |
832 | 832 | if (m_extraFeaturesUsed & kGLBufferMap) |
833 | 833 | { |
… |
… |
QStringList MythRenderOpenGL::GetDescription(void) |
1262 | 1262 | } |
1263 | 1263 | |
1264 | 1264 | bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect &Source, |
1265 | | const QRect &Destination, int Rotation) |
| 1265 | const QRect &Destination, int Rotation, qreal Scale) |
1266 | 1266 | { |
1267 | 1267 | if (!Texture || (Texture && Texture->m_size.isEmpty())) |
1268 | 1268 | return false; |
… |
… |
bool MythRenderOpenGL::UpdateTextureVertices(MythGLTexture *Texture, const QRect |
1301 | 1301 | data[4 + TEX_OFFSET] = data[6 + TEX_OFFSET]; |
1302 | 1302 | data[5 + TEX_OFFSET] = data[1 + TEX_OFFSET]; |
1303 | 1303 | |
1304 | | width = Texture->m_crop ? min(width, Destination.width()) : Destination.width(); |
1305 | | height = Texture->m_crop ? min(height, Destination.height()) : Destination.height(); |
| 1304 | width = Texture->m_crop ? min(static_cast<int>(width * Scale), Destination.width()) : Destination.width(); |
| 1305 | height = Texture->m_crop ? min(static_cast<int>(height * Scale), Destination.height()) : Destination.height(); |
1306 | 1306 | |
1307 | 1307 | data[2] = data[0] = Destination.left(); |
1308 | 1308 | 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); |