Add GLFW_SCALE_FRAMEBUFFER window hint

This adds the GLFW_SCALE_FRAMEBUFFER window hint, enabling control of
framebuffer scaling across Wayland and macOS.  On macOS, this window
hint is a new name for GLFW_COCOA_RETINA_FRAMEBUFFER, and both hint
names will modify the same hint.

This is now a more symmetric counterpart to GLFW_SCALE_TO_MONITOR and,
weirdly, they each apply neatly to half of the supported platforms.

This commit is mostly documentation updates to better integrate and
contrast these two scaling mechanisms.
This commit is contained in:
Camilla Löwy 2024-02-07 20:04:14 +01:00
parent 63397fb0d5
commit a9cc7c7260
12 changed files with 92 additions and 44 deletions

View File

@ -144,6 +144,7 @@ information on what to include when reporting a bug.
content area (#58) content area (#58)
- Added `GLFW_POSITION_X` and `GLFW_POSITION_Y` window hints for initial position - Added `GLFW_POSITION_X` and `GLFW_POSITION_Y` window hints for initial position
(#1603,#1747) (#1603,#1747)
- Added `GLFW_SCALE_FRAMEBUFFER` window hint for Wayland and macOS scaling (#2457)
- Added `GLFW_ANY_POSITION` hint value for letting the window manager choose (#1603,#1747) - Added `GLFW_ANY_POSITION` hint value for letting the window manager choose (#1603,#1747)
- Added `GLFW_PLATFORM_UNAVAILABLE` error for platform detection failures (#1958) - Added `GLFW_PLATFORM_UNAVAILABLE` error for platform detection failures (#1958)
- Added `GLFW_FEATURE_UNAVAILABLE` error for platform limitations (#1692) - Added `GLFW_FEATURE_UNAVAILABLE` error for platform limitations (#1692)

View File

@ -150,16 +150,8 @@ float xscale, yscale;
glfwGetMonitorContentScale(monitor, &xscale, &yscale); glfwGetMonitorContentScale(monitor, &xscale, &yscale);
``` ```
The content scale is the ratio between the current DPI and the platform's For more information on what the content scale is and how to use it, see
default DPI. This is especially important for text and any UI elements. If the [window content scale](@ref window_scale).
pixel dimensions of your UI scaled by this look appropriate on your machine then
it should appear at a reasonable size on other machines regardless of their DPI
and scaling settings. This relies on the system DPI and scaling settings being
somewhat correct.
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.
### Virtual position {#monitor_pos} ### Virtual position {#monitor_pos}

View File

@ -85,6 +85,22 @@ function pointers corresponding to the standard library functions `malloc`,
For more information see @ref init_allocator. For more information see @ref init_allocator.
#### Window hint for framebuffer scaling {#scale_framebuffer_34}
GLFW now allows provides the
[GLFW_SCALE_FRAMEBUFFER](@ref GLFW_SCALE_FRAMEBUFFER_hint) window hint for
controlling framebuffer scaling on platforms that handle scaling by keeping the
window size the same while resizing the framebuffer. The default value is to
allow framebuffer scaling.
This was already possible on macOS via the
[GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint) window
hint. This hint is now another name for
[GLFW_SCALE_FRAMEBUFFER](@ref GLFW_SCALE_FRAMEBUFFER_hint).
For more information, see @ref window_scale.
#### Window hints for initial position {#features_34_position_hint} #### Window hints for initial position {#features_34_position_hint}
GLFW now provides the @ref GLFW_POSITION_X and @ref GLFW_POSITION_Y window hints for GLFW now provides the @ref GLFW_POSITION_X and @ref GLFW_POSITION_Y window hints for
@ -288,6 +304,7 @@ then GLFW will fail to initialize.
- @ref GLFW_WAYLAND_LIBDECOR - @ref GLFW_WAYLAND_LIBDECOR
- @ref GLFW_WAYLAND_PREFER_LIBDECOR - @ref GLFW_WAYLAND_PREFER_LIBDECOR
- @ref GLFW_WAYLAND_DISABLE_LIBDECOR - @ref GLFW_WAYLAND_DISABLE_LIBDECOR
- @ref GLFW_SCALE_FRAMEBUFFER
## Release notes for earlier versions {#news_archive} ## Release notes for earlier versions {#news_archive}

View File

@ -239,13 +239,28 @@ focus when @ref glfwShowWindow is called. Possible values are `GLFW_TRUE` and
@anchor GLFW_SCALE_TO_MONITOR @anchor GLFW_SCALE_TO_MONITOR
__GLFW_SCALE_TO_MONITOR__ specified whether the window content area should be __GLFW_SCALE_TO_MONITOR__ specified whether the window content area should be
resized based on the [monitor content scale](@ref monitor_scale) of any monitor resized based on [content scale](@ref window_scale) changes. This can be
it is placed on. This includes the initial placement when the window is because of a global user settings change or because the window was moved to
created. Possible values are `GLFW_TRUE` and `GLFW_FALSE`. a monitor with different scale settings.
This hint only has an effect on platforms where screen coordinates and pixels This hint only has an effect on platforms where screen coordinates and pixels
always map 1:1 such as Windows and X11. On platforms like macOS the resolution always map 1:1, such as Windows and X11. On platforms like macOS the resolution
of the framebuffer is changed independently of the window size. of the framebuffer can change independently of the window size.
@anchor GLFW_SCALE_FRAMEBUFFER_hint
@anchor GLFW_COCOA_RETINA_FRAMEBUFFER_hint
__GLFW_SCALE_FRAMEBUFFER__ specifies whether the framebuffer should be resized
based on [content scale](@ref window_scale) changes. This can be
because of a global user settings change or because the window was moved to
a monitor with different scale settings.
This hint only has an effect on platforms where screen coordinates can be scaled
relative to pixel coordinates, such as macOS and Wayland. On platforms like
Windows and X11 the framebuffer and window content area sizes always map 1:1.
This is the new name, introduced in GLFW 3.4. The older
`GLFW_COCOA_RETINA_FRAMEBUFFER` name is also available for compatibility. Both
names modify the same hint value.
@anchor GLFW_MOUSE_PASSTHROUGH_hint @anchor GLFW_MOUSE_PASSTHROUGH_hint
__GLFW_MOUSE_PASSTHROUGH__ specifies whether the window is transparent to mouse __GLFW_MOUSE_PASSTHROUGH__ specifies whether the window is transparent to mouse
@ -474,11 +489,6 @@ GLFW behaves as if this hint was set to `GLFW_FALSE`. Possible values are
#### macOS specific hints {#window_hints_osx} #### macOS specific hints {#window_hints_osx}
@anchor GLFW_COCOA_RETINA_FRAMEBUFFER_hint
__GLFW_COCOA_RETINA_FRAMEBUFFER__ specifies whether to use full resolution
framebuffers on Retina displays. Possible values are `GLFW_TRUE` and
`GLFW_FALSE`. This is ignored on other platforms.
@anchor GLFW_COCOA_FRAME_NAME_hint @anchor GLFW_COCOA_FRAME_NAME_hint
__GLFW_COCOA_FRAME_NAME__ specifies the UTF-8 encoded name to use for autosaving __GLFW_COCOA_FRAME_NAME__ specifies the UTF-8 encoded name to use for autosaving
the window frame, or if empty disables frame autosaving for the window. This is the window frame, or if empty disables frame autosaving for the window. This is
@ -533,6 +543,7 @@ GLFW_CENTER_CURSOR | `GLFW_TRUE` | `GLFW_TRUE` or `GL
GLFW_TRANSPARENT_FRAMEBUFFER | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_TRANSPARENT_FRAMEBUFFER | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_FOCUS_ON_SHOW | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_FOCUS_ON_SHOW | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_SCALE_TO_MONITOR | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_SCALE_TO_MONITOR | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_SCALE_FRAMEBUFFER | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_MOUSE_PASSTHROUGH | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_MOUSE_PASSTHROUGH | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_POSITION_X | `GLFW_ANY_POSITION` | Any valid screen x-coordinate or `GLFW_ANY_POSITION` GLFW_POSITION_X | `GLFW_ANY_POSITION` | Any valid screen x-coordinate or `GLFW_ANY_POSITION`
GLFW_POSITION_Y | `GLFW_ANY_POSITION` | Any valid screen y-coordinate or `GLFW_ANY_POSITION` GLFW_POSITION_Y | `GLFW_ANY_POSITION` | Any valid screen y-coordinate or `GLFW_ANY_POSITION`
@ -563,7 +574,6 @@ GLFW_CONTEXT_DEBUG | `GLFW_FALSE` | `GLFW_TRUE` or `GL
GLFW_OPENGL_PROFILE | `GLFW_OPENGL_ANY_PROFILE` | `GLFW_OPENGL_ANY_PROFILE`, `GLFW_OPENGL_COMPAT_PROFILE` or `GLFW_OPENGL_CORE_PROFILE` GLFW_OPENGL_PROFILE | `GLFW_OPENGL_ANY_PROFILE` | `GLFW_OPENGL_ANY_PROFILE`, `GLFW_OPENGL_COMPAT_PROFILE` or `GLFW_OPENGL_CORE_PROFILE`
GLFW_WIN32_KEYBOARD_MENU | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_WIN32_KEYBOARD_MENU | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_WIN32_SHOWDEFAULT | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_WIN32_SHOWDEFAULT | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_COCOA_RETINA_FRAMEBUFFER | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_COCOA_FRAME_NAME | `""` | A UTF-8 encoded frame autosave name GLFW_COCOA_FRAME_NAME | `""` | A UTF-8 encoded frame autosave name
GLFW_COCOA_GRAPHICS_SWITCHING | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE` GLFW_COCOA_GRAPHICS_SWITCHING | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
GLFW_X11_CLASS_NAME | `""` | An ASCII encoded `WM_CLASS` class name GLFW_X11_CLASS_NAME | `""` | An ASCII encoded `WM_CLASS` class name
@ -736,16 +746,21 @@ float xscale, yscale;
glfwGetWindowContentScale(window, &xscale, &yscale); glfwGetWindowContentScale(window, &xscale, &yscale);
``` ```
The content scale is the ratio between the current DPI and the platform's The content scale can be thought of as the ratio between the current DPI and the
default DPI. This is especially important for text and any UI elements. If the platform's default DPI. It is intended to be a scaling factor to apply to the
pixel dimensions of your UI scaled by this look appropriate on your machine then pixel dimensions of text and other UI elements. If the dimensions scaled by
it should appear at a reasonable size on other machines regardless of their DPI this factor looks appropriate on your machine then it should appear at
and scaling settings. This relies on the system DPI and scaling settings being a reasonable size on other machines with different DPI and scaling settings.
somewhat correct.
This relies on the DPI and scaling settings on both machines being appropriate.
The content scale may depend on both the monitor resolution and pixel density
and on user settings like DPI or a scaling percentage. It may be very different
from the raw DPI calculated from the physical size and current resolution.
On systems where each monitors can have its own content scale, the window 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 content scale will depend on which monitor or monitors the system considers the
on. window to be "on".
If you wish to be notified when the content scale of a window changes, whether If you wish to be notified when the content scale of a window changes, whether
because of a system setting change or because it was moved to a monitor with because of a system setting change or because it was moved to a monitor with
@ -770,6 +785,18 @@ with a different content scale. To have this done automatically both when the
window is created and when its content scale later changes, set the @ref window is created and when its content scale later changes, set the @ref
GLFW_SCALE_TO_MONITOR window hint. GLFW_SCALE_TO_MONITOR window hint.
On platforms where pixels do not necessarily equal screen coordinates, the
framebuffer will instead need to be sized to provide a full resolution image
for the window. When the window moves between monitors with different content
scales, the window size will remain the same but the framebuffer size will
change. This is done automatically by default. To disable this resizing, set
the @ref GLFW_SCALE_FRAMEBUFFER window hint.
Both of these hints also apply when the window is created. Every window starts
out with a content scale of one. A window with one or both of these hints set
will adapt to the appropriate scale in the process of being created, set up and
shown.
### Window size limits {#window_sizelimits} ### Window size limits {#window_sizelimits}

View File

@ -1098,8 +1098,15 @@ extern "C" {
* [window hint](@ref GLFW_SCALE_TO_MONITOR). * [window hint](@ref GLFW_SCALE_TO_MONITOR).
*/ */
#define GLFW_SCALE_TO_MONITOR 0x0002200C #define GLFW_SCALE_TO_MONITOR 0x0002200C
/*! @brief macOS specific /*! @brief Window framebuffer scaling
* [window hint](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint). * [window hint](@ref GLFW_SCALE_FRAMEBUFFER_hint).
*/
#define GLFW_SCALE_FRAMEBUFFER 0x0002200D
/*! @brief Legacy name for compatibility.
*
* This is an alias for the
* [GLFW_SCALE_FRAMEBUFFER](@ref GLFW_SCALE_FRAMEBUFFER_hint) window hint for
* compatibility with earlier versions.
*/ */
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
/*! @brief macOS specific /*! @brief macOS specific
@ -3167,7 +3174,7 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
* *
* @remark @macos On OS X 10.10 and later the window frame will not be rendered * @remark @macos On OS X 10.10 and later the window frame will not be rendered
* at full resolution on Retina displays unless the * at full resolution on Retina displays unless the
* [GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint) * [GLFW_SCALE_FRAMEBUFFER](@ref GLFW_SCALE_FRAMEBUFFER_hint)
* hint is `GLFW_TRUE` and the `NSHighResolutionCapable` key is enabled in the * hint is `GLFW_TRUE` and the `NSHighResolutionCapable` key is enabled in the
* application bundle's `Info.plist`. For more information, see * application bundle's `Info.plist`. For more information, see
* [High Resolution Guidelines for OS X][hidpi-guide] in the Mac Developer * [High Resolution Guidelines for OS X][hidpi-guide] in the Mac Developer

View File

@ -145,7 +145,7 @@ typedef struct _GLFWwindowNS
GLFWbool maximized; GLFWbool maximized;
GLFWbool occluded; GLFWbool occluded;
GLFWbool retina; GLFWbool scaleFramebuffer;
// Cached window properties to filter out duplicate events // Cached window properties to filter out duplicate events
int width, height; int width, height;

View File

@ -513,7 +513,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
if (xscale != window->ns.xscale || yscale != window->ns.yscale) if (xscale != window->ns.xscale || yscale != window->ns.yscale)
{ {
if (window->ns.retina && window->ns.layer) if (window->ns.scaleFramebuffer && window->ns.layer)
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
window->ns.xscale = xscale; window->ns.xscale = xscale;
@ -872,7 +872,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
[window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)];
window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
window->ns.retina = wndconfig->ns.retina; window->ns.scaleFramebuffer = wndconfig->scaleFramebuffer;
if (fbconfig->transparent) if (fbconfig->transparent)
{ {
@ -1969,7 +1969,7 @@ VkResult _glfwCreateWindowSurfaceCocoa(VkInstance instance,
return VK_ERROR_EXTENSION_NOT_PRESENT; return VK_ERROR_EXTENSION_NOT_PRESENT;
} }
if (window->ns.retina) if (window->ns.scaleFramebuffer)
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
[window->ns.view setLayer:window->ns.layer]; [window->ns.view setLayer:window->ns.layer];

View File

@ -402,8 +402,8 @@ struct _GLFWwndconfig
GLFWbool focusOnShow; GLFWbool focusOnShow;
GLFWbool mousePassthrough; GLFWbool mousePassthrough;
GLFWbool scaleToMonitor; GLFWbool scaleToMonitor;
GLFWbool scaleFramebuffer;
struct { struct {
GLFWbool retina;
char frameName[256]; char frameName[256];
} ns; } ns;
struct { struct {

View File

@ -333,7 +333,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
forParameter:NSOpenGLContextParameterSurfaceOpacity]; forParameter:NSOpenGLContextParameterSurfaceOpacity];
} }
[window->ns.view setWantsBestResolutionOpenGLSurface:window->ns.retina]; [window->ns.view setWantsBestResolutionOpenGLSurface:window->ns.scaleFramebuffer];
[window->context.nsgl.object setView:window->ns.view]; [window->context.nsgl.object setView:window->ns.view];

View File

@ -274,6 +274,7 @@ void glfwDefaultWindowHints(void)
_glfw.hints.window.focusOnShow = GLFW_TRUE; _glfw.hints.window.focusOnShow = GLFW_TRUE;
_glfw.hints.window.xpos = GLFW_ANY_POSITION; _glfw.hints.window.xpos = GLFW_ANY_POSITION;
_glfw.hints.window.ypos = GLFW_ANY_POSITION; _glfw.hints.window.ypos = GLFW_ANY_POSITION;
_glfw.hints.window.scaleFramebuffer = GLFW_TRUE;
// The default is 24 bits of color, 24 bits of depth and 8 bits of stencil, // The default is 24 bits of color, 24 bits of depth and 8 bits of stencil,
// double buffered // double buffered
@ -288,9 +289,6 @@ void glfwDefaultWindowHints(void)
// The default is to select the highest available refresh rate // The default is to select the highest available refresh rate
_glfw.hints.refreshRate = GLFW_DONT_CARE; _glfw.hints.refreshRate = GLFW_DONT_CARE;
// The default is to use full Retina resolution framebuffers
_glfw.hints.window.ns.retina = GLFW_TRUE;
} }
GLFWAPI void glfwWindowHint(int hint, int value) GLFWAPI void glfwWindowHint(int hint, int value)
@ -374,9 +372,6 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_POSITION_Y: case GLFW_POSITION_Y:
_glfw.hints.window.ypos = value; _glfw.hints.window.ypos = value;
return; return;
case GLFW_COCOA_RETINA_FRAMEBUFFER:
_glfw.hints.window.ns.retina = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_WIN32_KEYBOARD_MENU: case GLFW_WIN32_KEYBOARD_MENU:
_glfw.hints.window.win32.keymenu = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.window.win32.keymenu = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
@ -389,6 +384,10 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_SCALE_TO_MONITOR: case GLFW_SCALE_TO_MONITOR:
_glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
case GLFW_SCALE_FRAMEBUFFER:
case GLFW_COCOA_RETINA_FRAMEBUFFER:
_glfw.hints.window.scaleFramebuffer = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_CENTER_CURSOR: case GLFW_CENTER_CURSOR:
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
return; return;

View File

@ -355,6 +355,7 @@ typedef struct _GLFWwindowWayland
GLFWbool fullscreen; GLFWbool fullscreen;
GLFWbool hovered; GLFWbool hovered;
GLFWbool transparent; GLFWbool transparent;
GLFWbool scaleFramebuffer;
struct wl_surface* surface; struct wl_surface* surface;
struct wl_callback* callback; struct wl_callback* callback;

View File

@ -369,6 +369,9 @@ void _glfwUpdateBufferScaleFromOutputsWayland(_GLFWwindow* window)
return; return;
} }
if (!window->wl.scaleFramebuffer)
return;
// Get the scale factor from the highest scale monitor. // Get the scale factor from the highest scale monitor.
int32_t maxScale = 1; int32_t maxScale = 1;
@ -980,6 +983,7 @@ static GLFWbool createNativeSurface(_GLFWwindow* window,
window->wl.bufferScale = 1; window->wl.bufferScale = 1;
window->wl.title = _glfw_strdup(wndconfig->title); window->wl.title = _glfw_strdup(wndconfig->title);
window->wl.appId = _glfw_strdup(wndconfig->wl.appId); window->wl.appId = _glfw_strdup(wndconfig->wl.appId);
window->wl.scaleFramebuffer = wndconfig->scaleFramebuffer;
window->wl.maximized = wndconfig->maximized; window->wl.maximized = wndconfig->maximized;