Automatic DPI for all platforms, per monitor DPI for Windows.

For Windows 8.1 and X11 (Linux, BSD) now use the DPI specified by the operating
system, which previously only worked on macOS. For Windows this is handled per
monitor, for X11 this is based on Xft.dpi or xrandr --dpi. This should result
in appropriate font and button sizes by default in most cases.

The UI has been simplified to a single UI Scale factor relative to the automatic
DPI, instead of two DPI and Virtual Pixel Size settings. There is forward and
backwards compatibility for existing user preferences.

Reviewed By: brecht, LazyDodo

Differential Revision: https://developer.blender.org/D2539
This commit is contained in:
Wouter 2017-03-11 03:27:08 +01:00 committed by Brecht Van Lommel
parent 86730f1f35
commit fe3fb23697
18 changed files with 267 additions and 57 deletions

View File

@ -907,6 +907,11 @@ extern int GHOST_UseNativePixels(void);
*/
extern float GHOST_GetNativePixelSize(GHOST_WindowHandle windowhandle);
/**
* Returns the suggested DPI for this window.
*/
extern GHOST_TUns16 GHOST_GetDPIHint(GHOST_WindowHandle windowhandle);
/**
* Enable IME attached to the given window, i.e. allows user-input
* events to be dispatched to the IME.

View File

@ -332,6 +332,12 @@ public:
virtual float getNativePixelSize(void) = 0;
/**
* Returns the recommended DPI for this window.
* \return The recommended DPI for this window.
*/
virtual GHOST_TUns16 getDPIHint() = 0;
#ifdef WITH_INPUT_IME
/**
* Enable IME attached to the given window, i.e. allows user-input

View File

@ -189,6 +189,7 @@ typedef enum {
GHOST_kEventWindowUpdate,
GHOST_kEventWindowSize,
GHOST_kEventWindowMove,
GHOST_kEventWindowDPIHintChanged,
GHOST_kEventDraggingEntered,
GHOST_kEventDraggingUpdated,

View File

@ -914,6 +914,12 @@ float GHOST_GetNativePixelSize(GHOST_WindowHandle windowhandle)
return 1.0f;
}
GHOST_TUns16 GHOST_GetDPIHint(GHOST_WindowHandle windowhandle)
{
GHOST_IWindow *window = (GHOST_IWindow *)windowhandle;
return window->getDPIHint();
}
#ifdef WITH_INPUT_IME
void GHOST_BeginIME(GHOST_WindowHandle windowhandle,

View File

@ -111,6 +111,11 @@
#define VK_MEDIA_PLAY_PAUSE 0xB3
#endif // VK_MEDIA_PLAY_PAUSE
// Window message newer than Windows 7
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif // WM_DPICHANGED
/* Workaround for some laptop touchpads, some of which seems to
* have driver issues which makes it so window function receives
* the message, but PeekMessage doesn't pick those messages for
@ -152,6 +157,27 @@ static void initRawInput()
#undef DEVICE_COUNT
}
#ifndef DPI_ENUMS_DECLARED
typedef enum PROCESS_DPI_AWARENESS {
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;
typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
#define USER_DEFAULT_SCREEN_DPI 96
#define DPI_ENUMS_DECLARED
#endif
typedef HRESULT(API * GHOST_WIN32_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);
typedef BOOL(API * GHOST_WIN32_EnableNonClientDpiScaling)(HWND);
GHOST_SystemWin32::GHOST_SystemWin32()
: m_hasPerformanceCounter(false), m_freq(0), m_start(0)
{
@ -161,6 +187,18 @@ GHOST_SystemWin32::GHOST_SystemWin32()
m_consoleStatus = 1;
// Tell Windows we are per monitor DPI aware. This disables the default
// blurry scaling and enables WM_DPICHANGED to allow us to draw at proper DPI.
HMODULE m_shcore = ::LoadLibrary("Shcore.dll");
if (m_shcore) {
GHOST_WIN32_SetProcessDpiAwareness fpSetProcessDpiAwareness =
(GHOST_WIN32_SetProcessDpiAwareness) ::GetProcAddress(m_shcore, "SetProcessDpiAwareness");
if (fpSetProcessDpiAwareness) {
fpSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
}
}
// Check if current keyboard layout uses AltGr and save keylayout ID for
// specialized handling if keys like VK_OEM_*. I.e. french keylayout
// generates VK_OEM_8 for their exclamation key (key left of right shift)
@ -922,6 +960,20 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
GHOST_ASSERT(system, "GHOST_SystemWin32::s_wndProc(): system not initialized");
if (hwnd) {
if(msg == WM_NCCREATE) {
// Tell Windows to automatically handle scaling of non-client areas
// such as the caption bar. EnableNonClientDpiScaling was introduced in Windows 10
HMODULE m_user32 = ::LoadLibrary("User32.dll");
if (m_user32) {
GHOST_WIN32_EnableNonClientDpiScaling fpEnableNonClientDpiScaling =
(GHOST_WIN32_EnableNonClientDpiScaling) ::GetProcAddress(m_user32, "EnableNonClientDpiScaling");
if (fpEnableNonClientDpiScaling) {
fpEnableNonClientDpiScaling(hwnd);
}
}
}
GHOST_WindowWin32 *window = (GHOST_WindowWin32 *)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (window) {
switch (msg) {
@ -1293,6 +1345,32 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
event = processWindowEvent(GHOST_kEventWindowMove, window);
}
break;
case WM_DPICHANGED:
/* The WM_DPICHANGED message is sent when the effective dots per inch (dpi) for a window has changed.
* The DPI is the scale factor for a window. There are multiple events that can cause the DPI to
* change such as when the window is moved to a monitor with a different DPI.
*/
{
WORD newYAxisDPI = HIWORD(wParam);
WORD newXAxisDPI = LOWORD(wParam);
// The suggested new size and position of the window.
RECT* const suggestedWindowRect = (RECT*)lParam;
// Push DPI change event first
system->pushEvent(processWindowEvent(GHOST_kEventWindowDPIHintChanged, window));
system->dispatchEvents();
eventHandled = true;
// Then move and resize window
SetWindowPos(hwnd,
NULL,
suggestedWindowRect->left,
suggestedWindowRect->top,
suggestedWindowRect->right - suggestedWindowRect->left,
suggestedWindowRect->bottom - suggestedWindowRect->top,
SWP_NOZORDER | SWP_NOACTIVATE);
}
break;
////////////////////////////////////////////////////////////////////////
// Window events, ignored

View File

@ -295,6 +295,15 @@ public:
return 1.0f;
}
/**
* Returns the recommended DPI for this window.
* \return The recommended DPI for this window.
*/
virtual inline GHOST_TUns16 getDPIHint()
{
return 96;
}
#ifdef WITH_INPUT_IME
virtual void beginIME(GHOST_TInt32 x,
GHOST_TInt32 y,

View File

@ -563,3 +563,19 @@ GHOST_WindowSDL::setWindowCursorVisibility(bool visible)
SDL_ShowCursor(visible);
return GHOST_kSuccess;
}
GHOST_TUns16
GHOST_WindowSDL::getDPIHint()
{
int displayIndex = SDL_GetWindowDisplayIndex(m_sdl_win);
if (displayIndex < 0) {
return 96;
}
float ddpi;
if (SDL_GetDisplayDPI(displayIndex, &ddpi, NULL, NULL) != 0) {
return 96;
}
return (int)ddpi;
}

View File

@ -168,6 +168,8 @@ protected:
GHOST_TSuccess beginFullScreen() const { return GHOST_kFailure; }
GHOST_TSuccess endFullScreen() const { return GHOST_kFailure; }
GHOST_TUns16 getDPIHint();
};

View File

@ -92,6 +92,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
m_tablet(0),
m_maxPressure(0),
m_normal_state(GHOST_kWindowStateNormal),
m_user32(NULL),
m_parentWindowHwnd(parentwindowhwnd),
m_debug_context(is_debug)
{
@ -965,6 +966,23 @@ void GHOST_WindowWin32::bringTabletContextToFront()
}
}
GHOST_TUns16 GHOST_WindowWin32::getDPIHint()
{
if (!m_user32) {
m_user32 = ::LoadLibrary("user32.dll");
}
if (m_user32) {
GHOST_WIN32_GetDpiForWindow fpGetDpiForWindow = (GHOST_WIN32_GetDpiForWindow) ::GetProcAddress(m_user32, "GetDpiForWindow");
if (fpGetDpiForWindow) {
return fpGetDpiForWindow(this->m_hWnd);
}
}
return USER_DEFAULT_SCREEN_DPI;
}
/** Reverse the bits in a GHOST_TUns8 */
static GHOST_TUns8 uns8ReverseBits(GHOST_TUns8 ch)
{

View File

@ -58,6 +58,12 @@ typedef BOOL (API * GHOST_WIN32_WTClose)(HCTX);
typedef BOOL (API * GHOST_WIN32_WTPacket)(HCTX, UINT, LPVOID);
typedef BOOL (API * GHOST_WIN32_WTOverlap)(HCTX, BOOL);
// typedefs for user32 functions to allow dynamic loading of Windows 10 DPI scaling functions
typedef UINT(API * GHOST_WIN32_GetDpiForWindow)(HWND);
#ifndef USER_DEFAULT_SCREEN_DPI
#define USER_DEFAULT_SCREEN_DPI 96
#endif // USER_DEFAULT_SCREEN_DPI
/**
* GHOST window on M$ Windows OSs.
* \author Maarten Gribnau
@ -251,6 +257,8 @@ public:
GHOST_TSuccess endFullScreen() const {return GHOST_kFailure;}
GHOST_TUns16 getDPIHint() override;
/** if the window currently resizing */
bool m_inLiveResize;
@ -351,6 +359,9 @@ private:
GHOST_TWindowState m_normal_state;
/** user32 dll handle*/
HMODULE m_user32;
/** Hwnd to parent window */
GHOST_TEmbedderWindowID m_parentWindowHwnd;

View File

@ -56,6 +56,9 @@
# include <X11/extensions/XInput2.h>
#endif
//For DPI value
#include <X11/Xresource.h>
#if defined(__sun__) || defined(__sun) || defined(__sparc) || defined(__sparc__) || defined(_AIX)
# include <strings.h>
#endif
@ -68,6 +71,7 @@
#include <algorithm>
#include <string>
#include <math.h>
/* For obscure full screen mode stuff
* lifted verbatim from blut. */
@ -1672,3 +1676,42 @@ endFullScreen() const
return GHOST_kSuccess;
}
GHOST_TUns16
GHOST_WindowX11::
getDPIHint()
{
/* Try to read DPI setting set using xrdb */
char* resMan = XResourceManagerString(m_display);
XrmDatabase xrdb = XrmGetStringDatabase(resMan);
if (xrdb) {
char* type = NULL;
XrmValue val;
int success = XrmGetResource(xrdb, "Xft.dpi", "Xft.Dpi", &type, &val);
if (success && type) {
if (strcmp(type, "String") == 0) {
return atoi((char*)val.addr);
}
}
}
/* Fallback to calculating DPI using X reported DPI, set using xrandr --dpi */
XWindowAttributes attr;
if (!XGetWindowAttributes(m_display, m_window, &attr)) {
/* Failed to get window attributes, return X11 default DPI */
return 96;
}
Screen* screen = attr.screen;
int pixelWidth = WidthOfScreen(screen);
int pixelHeight = HeightOfScreen(screen);
int mmWidth = WidthMMOfScreen(screen);
int mmHeight = HeightMMOfScreen(screen);
double pixelDiagonal = sqrt((pixelWidth * pixelWidth) + (pixelHeight * pixelHeight));
double mmDiagonal = sqrt((mmWidth * mmWidth) + (mmHeight * mmHeight));
float inchDiagonal = mmDiagonal * 0.039f;
int dpi = pixelDiagonal / inchDiagonal;
return dpi;
}

View File

@ -235,6 +235,8 @@ public:
GHOST_TSuccess endFullScreen() const;
GHOST_TUns16 getDPIHint();
protected:
/**
* \param type The type of rendering context create.

View File

@ -217,6 +217,7 @@ class USERPREF_PT_interface(Panel):
col = row.column()
col.label(text="Display:")
col.prop(view, "ui_scale", text="Scale")
col.prop(view, "show_tooltips")
col.prop(view, "show_tooltips_python")
col.prop(view, "show_object_info", text="Object Info")
@ -467,11 +468,6 @@ class USERPREF_PT_system(Panel):
col = colsplit.column()
col.label(text="General:")
col.prop(system, "dpi")
col.label("Virtual Pixel Mode:")
col.prop(system, "virtual_pixel_mode", text="")
col.separator()
col.prop(system, "frame_server_port")
col.prop(system, "scrollback", text="Console Scrollback")

View File

@ -68,6 +68,10 @@ void BLO_update_defaults_userpref_blend(void)
* but take care since some hardware has driver bugs here (T46962).
* Further hardware workarounds should be made in gpu_extensions.c */
U.glalphaclip = (1.0f / 255);
/* default so DPI is detected automatically */
U.dpi = 0;
U.ui_scale = 1.0f;
}
/**

View File

@ -467,9 +467,11 @@ typedef struct UserDef {
int audioformat;
int audiochannels;
int scrollback; /* console scrollback limit */
int dpi; /* range 48-128? */
char node_margin; /* node insert offset (aka auto-offset) margin, but might be useful for later stuff as well */
int scrollback; /* console scrollback limit */
int dpi; /* range 48-128? */
float ui_scale; /* interface scale */
int pad1;
char node_margin; /* node insert offset (aka auto-offset) margin, but might be useful for later stuff as well */
char pad2;
short transopts;
short menuthreshold1, menuthreshold2;

View File

@ -138,23 +138,11 @@ static void rna_userdef_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Pointe
}
/* also used by buffer swap switching */
static void rna_userdef_dpi_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
static void rna_userdef_dpi_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
{
/* font's are stored at each DPI level, without this we can easy load 100's of fonts */
BLF_cache_clear();
BKE_blender_userdef_refresh();
WM_main_add_notifier(NC_WINDOW, NULL); /* full redraw */
WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */
}
static void rna_userdef_virtual_pixel_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
{
/* font's are stored at each DPI level, without this we can easy load 100's of fonts */
BLF_cache_clear();
BKE_blender_userdef_refresh();
/* force setting drawable again */
wmWindowManager *wm = bmain->wm.first;
if (wm) {
@ -3325,6 +3313,12 @@ static void rna_def_userdef_view(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "View & Controls", "Preferences related to viewing data");
/* View */
prop = RNA_def_property(srna, "ui_scale", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_ui_text(prop, "UI Scale", "Changes the size of the fonts and buttons in the interface");
RNA_def_property_range(prop, 0.25f, 4.0f);
RNA_def_property_ui_range(prop, 0.5f, 2.0f, 1, 1);
RNA_def_property_float_default(prop, 1.0f);
RNA_def_property_update(prop, 0, "rna_userdef_dpi_update");
/* display */
prop = RNA_def_property(srna, "show_tooltips", PROP_BOOLEAN, PROP_NONE);
@ -3916,12 +3910,6 @@ static void rna_def_userdef_system(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL}
};
static EnumPropertyItem virtual_pixel_mode_items[] = {
{VIRTUAL_PIXEL_NATIVE, "NATIVE", 0, "Native", "Use native pixel size of the display"},
{VIRTUAL_PIXEL_DOUBLE, "DOUBLE", 0, "Double", "Use double the native pixel size of the display"},
{0, NULL, 0, NULL, NULL}
};
srna = RNA_def_struct(brna, "UserPreferencesSystem", NULL);
RNA_def_struct_sdna(srna, "UserDef");
RNA_def_struct_nested(brna, srna, "UserPreferences");
@ -3936,16 +3924,8 @@ static void rna_def_userdef_system(BlenderRNA *brna)
RNA_def_property_update(prop, NC_WINDOW, "rna_userdef_language_update");
prop = RNA_def_property(srna, "dpi", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "dpi");
RNA_def_property_range(prop, 48, 144);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "DPI", "Font size and resolution for display");
RNA_def_property_update(prop, 0, "rna_userdef_dpi_update");
prop = RNA_def_property(srna, "virtual_pixel_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "virtual_pixel");
RNA_def_property_enum_items(prop, virtual_pixel_mode_items);
RNA_def_property_ui_text(prop, "Virtual Pixel Mode", "Modify the pixel size for hi-res devices");
RNA_def_property_update(prop, 0, "rna_userdef_virtual_pixel_update");
prop = RNA_def_property(srna, "pixel_size", PROP_FLOAT, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

@ -77,6 +77,7 @@
#include "GPU_extensions.h"
#include "GPU_init_exit.h"
#include "GPU_glew.h"
#include "BLF_api.h"
/* for assert */
#ifndef NDEBUG
@ -374,14 +375,39 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win)
}
}
static float wm_window_get_virtual_pixelsize(void)
static void wm_window_set_dpi(wmWindow *win)
{
return ((U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1.0f : 2.0f);
}
int auto_dpi = GHOST_GetDPIHint(win->ghostwin);
float wm_window_pixelsize(wmWindow *win)
{
return (GHOST_GetNativePixelSize(win->ghostwin) * wm_window_get_virtual_pixelsize());
/* Lazily init UI scale size, preserving backwards compatibility by
* computing UI scale from ratio of previous DPI and auto DPI */
if (U.ui_scale == 0) {
int virtual_pixel = (U.virtual_pixel == VIRTUAL_PIXEL_NATIVE) ? 1 : 2;
if (U.dpi == 0) {
U.ui_scale = virtual_pixel;
}
else {
U.ui_scale = (virtual_pixel * U.dpi * 96.0f) / (auto_dpi * 72.0f);
}
CLAMP(U.ui_scale, 0.25f, 4.0f);
}
/* Blender's UI drawing assumes DPI 72 as a good default following macOS
* while Windows and Linux use DPI 96. GHOST assumes a default 96 so we
* remap the DPI to Blender's convention. */
int dpi = auto_dpi * U.ui_scale * (72.0/96.0f);
/* Automatically set larger pixel size for high DPI. */
int pixelsize = MAX2(1, dpi / 54);
/* Set user preferences globals for drawing, and for forward compatibility. */
U.pixelsize = GHOST_GetNativePixelSize(win->ghostwin) * pixelsize;
U.dpi = dpi / pixelsize;
U.virtual_pixel = (pixelsize == 1) ? VIRTUAL_PIXEL_NATIVE : VIRTUAL_PIXEL_DOUBLE;
BKE_blender_userdef_refresh();
}
/* belongs to below */
@ -456,10 +482,8 @@ static void wm_window_ghostwindow_add(wmWindowManager *wm, const char *title, wm
glClear(GL_COLOR_BUFFER_BIT);
}
/* displays with larger native pixels, like Macbook. Used to scale dpi with */
/* needed here, because it's used before it reads userdef */
U.pixelsize = wm_window_pixelsize(win);
BKE_blender_userdef_refresh();
wm_window_set_dpi(win);
wm_window_swap_buffers(win);
@ -626,7 +650,6 @@ wmWindow *WM_window_open_temp(bContext *C, const rcti *rect_init, int type)
Scene *scene = CTX_data_scene(C);
const char *title;
rcti rect = *rect_init;
const short px_virtual = (short)wm_window_get_virtual_pixelsize();
/* changes rect to fit within desktop */
wm_window_check_position(&rect);
@ -644,9 +667,8 @@ wmWindow *WM_window_open_temp(bContext *C, const rcti *rect_init, int type)
win->posy = rect.ymin;
}
/* multiply with virtual pixelsize, ghost handles native one (e.g. for retina) */
win->sizex = BLI_rcti_size_x(&rect) * px_virtual;
win->sizey = BLI_rcti_size_y(&rect) * px_virtual;
win->sizex = BLI_rcti_size_x(&rect);
win->sizey = BLI_rcti_size_y(&rect);
if (win->ghostwin) {
wm_window_set_size(win, win->sizex, win->sizey);
@ -835,8 +857,7 @@ void wm_window_make_drawable(wmWindowManager *wm, wmWindow *win)
GHOST_ActivateWindowDrawingContext(win->ghostwin);
/* this can change per window */
U.pixelsize = wm_window_pixelsize(win);
BKE_blender_userdef_refresh();
wm_window_set_dpi(win);
}
}
@ -1035,6 +1056,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
if (type == GHOST_kEventWindowSize) {
WM_jobs_stop(wm, win->screen, NULL);
}
wm_window_set_dpi(win);
/* win32: gives undefined window size when minimized */
if (state != GHOST_kWindowStateMinimized) {
@ -1118,7 +1141,19 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
}
break;
}
case GHOST_kEventWindowDPIHintChanged:
{
wm_window_set_dpi(win);
/* font's are stored at each DPI level, without this we can easy load 100's of fonts */
BLF_cache_clear();
BKE_blender_userdef_refresh();
WM_main_add_notifier(NC_WINDOW, NULL); /* full redraw */
WM_main_add_notifier(NC_SCREEN | NA_EDITED, NULL); /* refresh region sizes */
break;
}
case GHOST_kEventOpenMainFile:
{
PointerRNA props_ptr;
@ -1199,11 +1234,9 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr
{
// only update if the actual pixel size changes
float prev_pixelsize = U.pixelsize;
U.pixelsize = wm_window_pixelsize(win);
wm_window_set_dpi(win);
if (U.pixelsize != prev_pixelsize) {
BKE_blender_userdef_refresh();
// close all popups since they are positioned with the pixel
// size baked in and it's difficult to correct them
wmWindow *oldWindow = CTX_wm_window(C);

View File

@ -63,8 +63,6 @@ void wm_window_swap_buffers (wmWindow *win);
void wm_window_set_swap_interval(wmWindow *win, int interval);
bool wm_window_get_swap_interval(wmWindow *win, int *intervalOut);
float wm_window_pixelsize(wmWindow *win);
void wm_get_cursor_position (wmWindow *win, int *x, int *y);
void wm_cursor_position_from_ghost (wmWindow *win, int *x, int *y);
void wm_cursor_position_to_ghost (wmWindow *win, int *x, int *y);