Skip to content

Commit

Permalink
macos: Handle high DPI displays
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
mark-kendall committed Jun 29, 2020
1 parent 6d01c35 commit 19e8d9d
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 21 deletions.
2 changes: 1 addition & 1 deletion mythtv/libs/libmythtv/mythvideoout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 26 additions & 4 deletions mythtv/libs/libmythtv/videooutwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@

#define LOC QString("VideoWin: ")

#define SCALED_RECT(SRC, SCALE) QRect{ static_cast<int>(SRC.left() * SCALE), \
static_cast<int>(SRC.top() * SCALE), \
static_cast<int>(SRC.width() * SCALE), \
static_cast<int>(SRC.height() * SCALE) }

static float fix_aspect(float raw);
static float snap(float value, float snapto, float diff);

Expand All @@ -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)
Expand All @@ -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();
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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();
}
}
Expand Down
5 changes: 5 additions & 0 deletions mythtv/libs/libmythtv/videooutwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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};
Expand Down
4 changes: 3 additions & 1 deletion mythtv/libs/libmythui/mythpainter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ class UIEffects;
using LayoutVector = QVector<QTextLayout *>;
using FormatVector = QVector<QTextLayout::FormatRange>;

class MUI_PUBLIC MythPainter
class MUI_PUBLIC MythPainter : public QObject
{
Q_OBJECT

public:
MythPainter();
/** MythPainter destructor.
Expand Down
62 changes: 54 additions & 8 deletions mythtv/libs/libmythui/opengl/mythpainteropengl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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);
Expand All @@ -110,13 +127,17 @@ void MythOpenGLPainter::Begin(QPaintDevice *Parent)
buf = m_render->CreateVBO(static_cast<int>(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<float>(m_lastSize.width() + 1) * m_lastSize.height()) / s_onehd;
int cpu = qMax(static_cast<int>(hdscreens * s_basesize), s_basesize);
int gpu = cpu * 3 / 2;
Expand All @@ -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();
}
Expand Down Expand Up @@ -222,38 +246,60 @@ 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<int>(Dest.left() * m_pixelRatio),
static_cast<int>(Dest.top() * m_pixelRatio),
static_cast<int>(Dest.width() * m_pixelRatio),
static_cast<int>(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))
{
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)
m_mappedBufferPoolIdx = 0;
}
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;
Expand All @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions mythtv/libs/libmythui/opengl/mythpainteropengl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <QQueue>

// MythTV
#include "mythdisplay.h"
#include "mythpainter.h"
#include "mythimage.h"

Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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<MythImage *, MythGLTexture*> m_imageToTextureMap;
std::list<MythImage *> m_ImageExpireList;
Expand Down
10 changes: 5 additions & 5 deletions mythtv/libs/libmythui/opengl/mythrenderopengl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<int>(width * Scale), Destination.width()) : Destination.width();
height = Texture->m_crop ? min(static_cast<int>(height * Scale), Destination.height()) : Destination.height();

data[2] = data[0] = Destination.left();
data[5] = data[1] = Destination.top();
Expand Down
4 changes: 2 additions & 2 deletions mythtv/libs/libmythui/opengl/mythrenderopengl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 19e8d9d

Please sign in to comment.