WARNING: Support high DPI displays

This change could break things. So, I created SUPPORT_HIGH_DPI flag to enable it (disabled by default).

Basically, it detects HighDPI display and scales all drawing (and mouse input) appropiately to match the equivalent "standardDPI" screen size on highDPI. It uses screenScaling matrix to do that.

This scaling comes with some undesired effects, like aliasing on default font text (keep in mind that font is pixel-perfect, not intended for any non-rounded scale factor).

The only solution for this aliasing would be some AA postpro filter or implementing the highDPI scaling in a different way: rendering to a texture and scaling it with FILTER_BILINEAR, check `core_window_scale_letterbox.c` example for reference.

Use at your own risk.
This commit is contained in:
Ray
2019-05-01 14:30:36 +02:00
parent 270f563964
commit bb2841a26d
2 changed files with 59 additions and 75 deletions

View File

@ -50,6 +50,8 @@
#define SUPPORT_SCREEN_CAPTURE 1 #define SUPPORT_SCREEN_CAPTURE 1
// Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() // Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback()
#define SUPPORT_GIF_RECORDING 1 #define SUPPORT_GIF_RECORDING 1
// Allow scale all the drawn content to match the high-DPI equivalent size (only PLATFORM_DESKTOP)
//#define SUPPORT_HIGH_DPI 1
//------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------

View File

@ -59,6 +59,9 @@
* #define SUPPORT_GIF_RECORDING * #define SUPPORT_GIF_RECORDING
* Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() * Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback()
* *
* #define SUPPORT_HIGH_DPI
* Allow scale all the drawn content to match the high-DPI equivalent size (only PLATFORM_DESKTOP)
*
* DEPENDENCIES: * DEPENDENCIES:
* rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) * rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly)
* raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) * raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion)
@ -437,7 +440,7 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads default fo
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
static bool InitGraphicsDevice(int width, int height); // Initialize graphics device static bool InitGraphicsDevice(int width, int height); // Initialize graphics device
static void SetupFramebuffer(int width, int height); // Setup main framebuffer static void SetupFramebuffer(int width, int height); // Setup main framebuffer
static void SetupViewport(void); // Set viewport parameters static void SetupViewport(int width, int height); // Set viewport for a provided width and height
static void SwapBuffers(void); // Copy back buffer to front buffers static void SwapBuffers(void); // Copy back buffer to front buffers
static void InitTimer(void); // Initialize timer static void InitTimer(void); // Initialize timer
@ -1175,6 +1178,7 @@ void BeginMode2D(Camera2D camera)
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
rlLoadIdentity(); // Reset current matrix (MODELVIEW) rlLoadIdentity(); // Reset current matrix (MODELVIEW)
rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required
// Camera rotation and scaling is always relative to target // Camera rotation and scaling is always relative to target
Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f);
@ -1193,6 +1197,7 @@ void EndMode2D(void)
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
rlLoadIdentity(); // Reset current matrix (MODELVIEW) rlLoadIdentity(); // Reset current matrix (MODELVIEW)
rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required
} }
// Initializes 3D mode with custom camera (3D) // Initializes 3D mode with custom camera (3D)
@ -1246,6 +1251,8 @@ void EndMode3D(void)
rlMatrixMode(RL_MODELVIEW); // Get back to modelview matrix rlMatrixMode(RL_MODELVIEW); // Get back to modelview matrix
rlLoadIdentity(); // Reset current matrix (MODELVIEW) rlLoadIdentity(); // Reset current matrix (MODELVIEW)
rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required
rlDisableDepthTest(); // Disable DEPTH_TEST for 2D rlDisableDepthTest(); // Disable DEPTH_TEST for 2D
} }
@ -1284,22 +1291,8 @@ void EndTextureMode(void)
rlDisableRenderTexture(); // Disable render target rlDisableRenderTexture(); // Disable render target
// Set viewport to default framebuffer size (screen size) // Set viewport to default framebuffer size
SetupViewport(); SetupViewport(renderWidth, renderHeight);
rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix
rlLoadIdentity(); // Reset current matrix (PROJECTION)
// Set orthographic projection to current framebuffer size
// NOTE: Configured top-left corner as (0, 0)
rlOrtho(0, GetScreenWidth(), GetScreenHeight(), 0, 0.0f, 1.0f);
rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix
rlLoadIdentity(); // Reset current matrix (MODELVIEW)
// Reset current screen size
currentWidth = GetScreenWidth();
currentHeight = GetScreenHeight();
} }
// Returns a ray trace from mouse position // Returns a ray trace from mouse position
@ -2336,12 +2329,11 @@ static bool InitGraphicsDevice(int width, int height)
currentWidth = width; currentWidth = width;
currentHeight = height; currentHeight = height;
screenScaling = MatrixIdentity(); // No draw scaling required by default
// NOTE: Framebuffer (render area - renderWidth, renderHeight) could include black bars... // NOTE: Framebuffer (render area - renderWidth, renderHeight) could include black bars...
// ...in top-down or left-right to match display aspect ratio (no weird scalings) // ...in top-down or left-right to match display aspect ratio (no weird scalings)
// Screen scaling matrix is required in case desired screen area is different than display area
screenScaling = MatrixIdentity();
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
glfwSetErrorCallback(ErrorCallback); glfwSetErrorCallback(ErrorCallback);
@ -2388,7 +2380,7 @@ static bool InitGraphicsDevice(int width, int height)
//glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window
//glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API
//glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers
#if defined(PLATFORM_DESKTOP) #if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI)
// NOTE: If using external GLFW, it requires latest GLFW 3.3 for this functionality // NOTE: If using external GLFW, it requires latest GLFW 3.3 for this functionality
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on
#endif #endif
@ -2468,9 +2460,11 @@ static bool InitGraphicsDevice(int width, int height)
// framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched
// by the sides to fit all monitor space... // by the sides to fit all monitor space...
// At this point we need to manage render size vs screen size // Try to setup the most appropiate fullscreen framebuffer for the requested screenWidth/screenHeight
// NOTE: This function uses and modifies global module variables: // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset)
// screenWidth/screenHeight - renderWidth/renderHeight - screenScaling // Modified global variables: screenWidth/screenHeight - renderWidth/renderHeight - renderOffsetX/renderOffsetY - screenScaling
// TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed...
// HighDPI monitors are properly considered in a following similar function: SetupViewport()
SetupFramebuffer(displayWidth, displayHeight); SetupFramebuffer(displayWidth, displayHeight);
window = glfwCreateWindow(displayWidth, displayHeight, windowTitle, glfwGetPrimaryMonitor(), NULL); window = glfwCreateWindow(displayWidth, displayHeight, windowTitle, glfwGetPrimaryMonitor(), NULL);
@ -2494,12 +2488,6 @@ static bool InitGraphicsDevice(int width, int height)
if (windowPosY < 0) windowPosY = 0; if (windowPosY < 0) windowPosY = 0;
glfwSetWindowPos(window, windowPosX, windowPosY); glfwSetWindowPos(window, windowPosX, windowPosY);
// Get window HiDPI scaling factor
float scaleRatio = 0.0f;
glfwGetWindowContentScale(window, &scaleRatio, NULL);
scaleRatio = roundf(scaleRatio);
//screenScaling = MatrixScale(scaleRatio, scaleRatio, scaleRatio);
#endif #endif
renderWidth = screenWidth; renderWidth = screenWidth;
renderHeight = screenHeight; renderHeight = screenHeight;
@ -2886,23 +2874,23 @@ static bool InitGraphicsDevice(int width, int height)
} }
#endif // PLATFORM_ANDROID || PLATFORM_RPI #endif // PLATFORM_ANDROID || PLATFORM_RPI
renderWidth = screenWidth;
renderHeight = screenHeight;
// Initialize OpenGL context (states and resources) // Initialize OpenGL context (states and resources)
// NOTE: screenWidth and screenHeight not used, just stored as globals // NOTE: screenWidth and screenHeight not used, just stored as globals in rlgl
rlglInit(screenWidth, screenHeight); rlglInit(screenWidth, screenHeight);
// Setup default viewport int fbWidth = renderWidth;
SetupViewport(); int fbHeight = renderHeight;
// Initialize internal projection and modelview matrices #if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI)
// NOTE: Default to orthographic projection mode with top-left corner at (0,0) glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix
rlLoadIdentity(); // Reset current matrix (PROJECTION) // Screen scaling matrix is required in case desired screen area is different than display area
rlOrtho(0, renderWidth - renderOffsetX, renderHeight - renderOffsetY, 0, 0.0f, 1.0f); screenScaling = MatrixScale((float)fbWidth/screenWidth, (float)fbHeight/screenHeight, 1.0f);
rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix SetMouseScale((float)screenWidth/fbWidth, (float)screenHeight/fbHeight);
rlLoadIdentity(); // Reset current matrix (MODELVIEW) #endif // PLATFORM_DESKTOP && SUPPORT_HIGH_DPI
// Setup default viewport
SetupViewport(fbWidth, fbHeight);
ClearBackground(RAYWHITE); // Default background color for raylib games :P ClearBackground(RAYWHITE); // Default background color for raylib games :P
@ -2912,21 +2900,32 @@ static bool InitGraphicsDevice(int width, int height)
return true; return true;
} }
// Set viewport parameters // Set viewport for a provided width and height
static void SetupViewport(void) static void SetupViewport(int width, int height)
{ {
#if defined(__APPLE__) renderWidth = width;
// Get framebuffer size of current window renderHeight = height;
// NOTE: Required to handle HighDPI display correctly on OSX because framebuffer is automatically reasized to adapt to new DPI.
// When OS does that, it can be detected using GLFW3 callback: glfwSetFramebufferSizeCallback() // Set viewport width and height
int fbWidth, fbHeight; // NOTE: We consider render size and offset in case black bars are required and
glfwGetFramebufferSize(window, &fbWidth, &fbHeight); // render area does not match full display area (this situation is only applicable on fullscreen mode)
rlViewport(renderOffsetX/2, renderOffsetY/2, fbWidth - renderOffsetX, fbHeight - renderOffsetY);
#else
// Initialize screen viewport (area of the screen that you will actually draw to)
// NOTE: Viewport must be recalculated if screen is resized
rlViewport(renderOffsetX/2, renderOffsetY/2, renderWidth - renderOffsetX, renderHeight - renderOffsetY); rlViewport(renderOffsetX/2, renderOffsetY/2, renderWidth - renderOffsetX, renderHeight - renderOffsetY);
#endif
rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix
rlLoadIdentity(); // Reset current matrix (PROJECTION)
// Set orthographic projection to current framebuffer size
// NOTE: Configured top-left corner as (0, 0)
rlOrtho(0, renderWidth, renderHeight, 0, 0.0f, 1.0f);
rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix
rlLoadIdentity(); // Reset current matrix (MODELVIEW)
// Window size must be updated to be used on 3D mode to get new aspect ratio (BeginMode3D())
// NOTE: Be careful! GLFW3 will choose the closest fullscreen resolution supported by current monitor,
// for example, if reescaling back to 800x450 (desired), it could set 720x480 (closest fullscreen supported)
currentWidth = screenWidth;
currentHeight = screenHeight;
} }
// Compute framebuffer size relative to screen size and display size // Compute framebuffer size relative to screen size and display size
@ -2959,7 +2958,7 @@ static void SetupFramebuffer(int width, int height)
// Screen scaling required // Screen scaling required
float scaleRatio = (float)renderWidth/(float)screenWidth; float scaleRatio = (float)renderWidth/(float)screenWidth;
screenScaling = MatrixScale(scaleRatio, scaleRatio, scaleRatio); screenScaling = MatrixScale(scaleRatio, scaleRatio, 1.0f);
// NOTE: We render to full display resolution! // NOTE: We render to full display resolution!
// We just need to calculate above parameters for downscale matrix and offsets // We just need to calculate above parameters for downscale matrix and offsets
@ -3705,24 +3704,7 @@ static void CursorEnterCallback(GLFWwindow *window, int enter)
// NOTE: Window resizing not allowed by default // NOTE: Window resizing not allowed by default
static void WindowSizeCallback(GLFWwindow *window, int width, int height) static void WindowSizeCallback(GLFWwindow *window, int width, int height)
{ {
// If window is resized, viewport and projection matrix needs to be re-calculated SetupViewport(width, height); // Reset viewport and projection matrix for new size
rlViewport(0, 0, width, height); // Set viewport width and height
rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix
rlLoadIdentity(); // Reset current matrix (PROJECTION)
rlOrtho(0, width, height, 0, 0.0f, 1.0f); // Orthographic projection mode with top-left corner at (0,0)
rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix
rlLoadIdentity(); // Reset current matrix (MODELVIEW)
rlClearScreenBuffers(); // Clear screen buffers (color and depth)
// Window size must be updated to be used on 3D mode to get new aspect ratio (BeginMode3D())
// NOTE: Be careful! GLFW3 will choose the closest fullscreen resolution supported by current monitor,
// for example, if reescaling back to 800x450 (desired), it could set 720x480 (closest fullscreen supported)
screenWidth = width;
screenHeight = height;
renderWidth = width;
renderHeight = height;
currentWidth = width;
currentHeight = height;
// NOTE: Postprocessing texture is not scaled to new size // NOTE: Postprocessing texture is not scaled to new size