Skip to content

Commit

Permalink
Add content scale queries
Browse files Browse the repository at this point in the history
This adds glfwGetWindowContentScale and glfwGetMonitorContentScale for
querying the recommended drawing scale factor for DPI-aware rendering.

Parts of this patch are based on code by @ferreiradaselva.

Fixes #235.
Fixes #439.
Fixes #677.
Fixes #845.
Fixes #898.
  • Loading branch information
elmindreda committed Oct 26, 2017
1 parent 1be81a1 commit f3bd6ce
Show file tree
Hide file tree
Showing 26 changed files with 384 additions and 25 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -136,6 +136,8 @@ information on what to include when reporting a bug.
gamepad mapping (#900)
- Added `glfwGetGamepadState` function, `GLFW_GAMEPAD_*` and `GLFWgamepadstate`
for retrieving gamepad input state (#900)
- Added `glfwGetWindowContentScale` and `glfwGetMonitorContentScale` for
DPI-aware rendering (#235,#439,#677,#845,#898)
- Added `glfwRequestWindowAttention` function for requesting attention from the
user (#732,#988)
- Added `glfwGetKeyScancode` function that allows retrieving platform dependent
Expand Down
27 changes: 22 additions & 5 deletions docs/monitor.dox
Expand Up @@ -131,17 +131,34 @@ current _resolution_, i.e. the width and height of its current
[video mode](@ref monitor_modes).

@code
int widthMM, heightMM;
glfwGetMonitorPhysicalSize(monitor, &widthMM, &heightMM);
int width_mm, height_mm;
glfwGetMonitorPhysicalSize(monitor, &width_mm, &height_mm);
@endcode

This can, for example, be used together with the current video mode to calculate
the DPI of a monitor.
While this can be used to calculate the raw DPI of a monitor, this is often not
useful. Instead use the [monitor content scale](@ref monitor_scale) and
[window content scale](@ref window_scale) to scale your content.


@subsection monitor_scale Content scale

The content scale for a monitor can be retrieved with @ref
glfwGetMonitorContentScale.

@code
const double dpi = mode->width / (widthMM / 25.4);
float xscale, yscale;
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
@endcode

The content scale is the ratio between the current DPI and the platform's
default DPI. If you scale all pixel dimensions by this scale then your content
should appear at an appropriate size. This is especially important for text and
any UI elements.

The content scale may depend on both the monitor resolution and pixel density
and on user settings. It may be very different from the raw DPI calculated from
the physical size and current resolution.


@subsection monitor_pos Virtual position

Expand Down
9 changes: 9 additions & 0 deletions docs/news.dox
Expand Up @@ -58,6 +58,15 @@ windows with @ref glfwSetWindowAttrib.
@see @ref window_attribs


@subsection news_33_contentscale Content scale queries for DPI-aware rendering

GLFW now supports querying the window and monitor content scale, i.e. the ratio
between the current DPI and the platform's default DPI, with @ref
glfwGetWindowContentScale and @ref glfwGetMonitorContentScale.

@see @ref window_scale


@subsection news_33_inithint Support for initialization hints

GLFW now supports setting library initialization hints with @ref glfwInitHint
Expand Down
20 changes: 20 additions & 0 deletions docs/window.dox
Expand Up @@ -663,6 +663,26 @@ The size of a framebuffer may change independently of the size of a window, for
example if the window is dragged between a regular monitor and a high-DPI one.


@subsection window_scale Window content scale

The content scale for a window can be retrieved with @ref
glfwGetWindowContentScale.

@code
float xscale, yscale;
glfwGetWindowContentScale(window, &xscale, &yscale);
@endcode

The content scale of a window is the ratio between the current DPI and the
platform's default DPI. If you scale all pixel dimensions by this scale then
your content should appear at an appropriate size. This is especially important
for text and any UI elements.

On systems where each monitors can have its own content scale, the window
content scale will depend on which monitor the system considers the window to be
on.


@subsection window_sizelimits Window size limits

The minimum and maximum size of the client area of a windowed mode window can be
Expand Down
60 changes: 60 additions & 0 deletions include/GLFW/glfw3.h
Expand Up @@ -1926,6 +1926,36 @@ GLFWAPI void glfwGetMonitorPos(GLFWmonitor* monitor, int* xpos, int* ypos);
*/
GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* monitor, int* widthMM, int* heightMM);

/*! @brief Retrieves the content scale for the specified monitor.
*
* This function retrieves the content scale for the specified monitor. The
* content scale is the ratio between the current DPI and the platform's
* default DPI. If you scale all pixel dimensions by this scale then your
* content should appear at an appropriate size. This is especially important
* for text and any UI elements.
*
* The content scale may depend on both the monitor resolution and pixel
* density and on user settings. It may be very different from the raw DPI
* calculated from the physical size and current resolution.
*
* @param[in] monitor The monitor to query.
* @param[out] xscale Where to store the x-axis content scale, or `NULL`.
* @param[out] yscale Where to store the y-axis content scale, or `NULL`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref monitor_scale
* @sa @ref glfwGetWindowContentScale
*
* @since Added in version 3.3.
*
* @ingroup monitor
*/
GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, float* yscale);

/*! @brief Returns the name of the specified monitor.
*
* This function returns a human-readable name, encoded as UTF-8, of the
Expand Down Expand Up @@ -2774,6 +2804,36 @@ GLFWAPI void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height)
*/
GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int* right, int* bottom);

/*! @brief Retrieves the content scale for the specified window.
*
* This function retrieves the content scale for the specified window. The
* content scale is the ratio between the current DPI and the platform's
* default DPI. If you scale all pixel dimensions by this scale then your
* content should appear at an appropriate size. This is especially important
* for text and any UI elements.
*
* On systems where each monitors can have its own content scale, the window
* content scale will depend on which monitor the system considers the window
* to be on.
*
* @param[in] window The window to query.
* @param[out] xscale Where to store the x-axis content scale, or `NULL`.
* @param[out] yscale Where to store the y-axis content scale, or `NULL`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_scale
* @sa @ref glfwGetMonitorContentScale
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);

/*! @brief Iconifies the specified window.
*
* This function iconifies (minimizes) the specified window if it was
Expand Down
45 changes: 45 additions & 0 deletions src/cocoa_monitor.m
Expand Up @@ -229,6 +229,9 @@ void _glfwPollMonitorsNS(void)
displays = calloc(displayCount, sizeof(CGDirectDisplayID));
CGGetOnlineDisplayList(displayCount, displays, &displayCount);

for (i = 0; i < _glfw.monitorCount; i++)
_glfw.monitors[i]->ns.screen = nil;

disconnectedCount = _glfw.monitorCount;
if (disconnectedCount)
{
Expand Down Expand Up @@ -371,6 +374,48 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
*ypos = (int) bounds.origin.y;
}

void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale)
{
if (!monitor->ns.screen)
{
NSUInteger i;
NSArray* screens = [NSScreen screens];

for (i = 0; i < [screens count]; i++)
{
NSScreen* screen = [screens objectAtIndex:i];
NSNumber* displayID =
[[screen deviceDescription] objectForKey:@"NSScreenNumber"];

// HACK: Compare unit numbers instead of display IDs to work around
// display replacement on machines with automatic graphics
// switching
if (monitor->ns.unitNumber ==
CGDisplayUnitNumber([displayID unsignedIntValue]))
{
monitor->ns.screen = screen;
break;
}
}

if (i == [screens count])
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to find a screen for monitor");
return;
}
}

const NSRect points = [monitor->ns.screen frame];
const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];

if (xscale)
*xscale = (float) (pixels.size.width / points.size.width);
if (yscale)
*yscale = (float) (pixels.size.height / points.size.height);
}

GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
{
CFArrayRef modes;
Expand Down
1 change: 1 addition & 0 deletions src/cocoa_platform.h
Expand Up @@ -135,6 +135,7 @@ typedef struct _GLFWmonitorNS
CGDirectDisplayID displayID;
CGDisplayModeRef previousMode;
uint32_t unitNumber;
id screen;

} _GLFWmonitorNS;

Expand Down
12 changes: 12 additions & 0 deletions src/cocoa_window.m
Expand Up @@ -1288,6 +1288,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
*bottom = contentRect.origin.y - frameRect.origin.y;
}

void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale)
{
const NSRect points = [window->ns.view frame];
const NSRect pixels = [window->ns.view convertRectToBacking:points];

if (xscale)
*xscale = (float) (pixels.size.width / points.size.width);
if (yscale)
*yscale = (float) (pixels.size.height / points.size.height);
}

void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
[window->ns.object miniaturize:nil];
Expand Down
2 changes: 2 additions & 0 deletions src/internal.h
Expand Up @@ -641,6 +641,7 @@ const char* _glfwPlatformGetScancodeName(int scancode);
int _glfwPlatformGetKeyScancode(int key);

void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, float* xscale, float* yscale);
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
Expand Down Expand Up @@ -670,6 +671,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int min
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom);
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale);
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
Expand Down
9 changes: 9 additions & 0 deletions src/mir_monitor.c
Expand Up @@ -88,6 +88,15 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
*ypos = monitor->mir.y;
}

void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale)
{
if (xscale)
*xscale = 1.f;
if (yscale)
*yscale = 1.f;
}

static void FillInRGBBitsFromPixelFormat(GLFWvidmode* mode, const MirPixelFormat pf)
{
switch (pf)
Expand Down
9 changes: 9 additions & 0 deletions src/mir_window.c
Expand Up @@ -515,6 +515,15 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
*height = window->mir.height;
}

void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale)
{
if (xscale)
*xscale = 1.f;
if (yscale)
*yscale = 1.f;
}

void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
MirWindowSpec* spec;
Expand Down
15 changes: 15 additions & 0 deletions src/monitor.c
Expand Up @@ -314,6 +314,21 @@ GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* handle, int* widthMM, int*
*heightMM = monitor->heightMM;
}

GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* handle,
float* xscale, float* yscale)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);

if (xscale)
*xscale = 0.f;
if (yscale)
*yscale = 0.f;

_GLFW_REQUIRE_INIT();
_glfwPlatformGetMonitorContentScale(monitor, xscale, yscale);
}

GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
Expand Down
9 changes: 9 additions & 0 deletions src/null_monitor.c
Expand Up @@ -36,6 +36,15 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
{
}

void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale)
{
if (xscale)
*xscale = 1.f;
if (yscale)
*yscale = 1.f;
}

GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* found)
{
return NULL;
Expand Down
9 changes: 9 additions & 0 deletions src/null_window.c
Expand Up @@ -139,6 +139,15 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
{
}

void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale)
{
if (xscale)
*xscale = 1.f;
if (yscale)
*yscale = 1.f;
}

void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
}
Expand Down
2 changes: 2 additions & 0 deletions src/win32_init.c
Expand Up @@ -151,6 +151,8 @@ static GLFWbool loadLibraries(void)
{
_glfw.win32.shcore.SetProcessDpiAwareness_ = (PFN_SetProcessDpiAwareness)
GetProcAddress(_glfw.win32.shcore.instance, "SetProcessDpiAwareness");
_glfw.win32.shcore.GetDpiForMonitor_ = (PFN_GetDpiForMonitor)
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
}

return GLFW_TRUE;
Expand Down

3 comments on commit f3bd6ce

@ghuser404
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elmindreda, it seems there is a slight difference in style: widthMM, heightMM vs width_mm, height_mm.

@elmindreda
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ghuser404 Yup. Example code deliberately uses a slightly different style as a placeholder for the user's style. I'd accidentally used the GLFW style for those two variables and took the liberty of fixing it while I was updating things nearby.

@ghuser404
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elmindreda, makes sense. Thanks for clarification!

Please sign in to comment.