opengl surface/renderer: set surface scaling according to dpi of first monitor. Use that dpi to scale backing texture of canvas renderer. Note: changing monitor isn't handled yet!

This commit is contained in:
martinfouilleul 2023-02-08 18:22:54 +01:00
parent 7fbc4ba270
commit 7cf4c3d925
14 changed files with 144 additions and 87 deletions

View File

@ -1,4 +1,4 @@
set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext
cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GL /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/perf_text.exe
cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GL /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib user32.lib opengl32.lib gdi32.lib shcore.lib /out:../../bin/perf_text.exe

View File

@ -81,6 +81,8 @@ int main()
mp_rect rect = {.x = 100, .y = 100, .w = 980, .h = 600};
mp_window window = mp_window_create(rect, "test", 0);
mp_rect contentRect = mp_window_get_content_rect(window);
//NOTE: create surface, canvas and font
#if defined(OS_MACOS)
@ -90,12 +92,13 @@ int main()
#else
#error "unsupported OS"
#endif
mg_surface_swap_interval(surface, 0);
mg_canvas canvas = mg_canvas_create(surface);
mg_font font = create_font();
mg_font_extents extents = mg_font_get_extents(font);
f32 fontScale = mg_font_get_scale_for_em_pixels(font, 12);
f32 fontScale = mg_font_get_scale_for_em_pixels(font, 14);
f32 lineHeight = fontScale*(extents.ascent + extents.descent + extents.leading);
@ -130,14 +133,14 @@ int main()
}
f32 textX = 10;
f32 textY = 600 - lineHeight;
f32 textY = contentRect.h - lineHeight - 10;
mg_surface_prepare(surface);
mg_set_color_rgba(1, 1, 1, 1);
mg_clear();
mg_set_font(font);
mg_set_font_size(12);
mg_set_font_size(14);
mg_set_color_rgba(0, 0, 0, 1);
mg_move_to(textX, textY);
@ -155,7 +158,7 @@ int main()
break;
}
}
ASSERT(subIndex < 512 && (startIndex+subIndex)<=codePointCount);
u32 glyphs[512];
mg_font_get_glyph_indices(font, (str32){subIndex, codePoints+startIndex}, (str32){512, glyphs});
@ -171,12 +174,10 @@ int main()
startIndex += subIndex;
}
f64 startFlushTime = mp_get_time(MP_CLOCK_MONOTONIC);
mg_set_color_rgba(0, 0, 1, 1);
mg_set_font(font);
mg_set_font_size(12);
mg_move_to(50, 50);
mg_set_font_size(14);
mg_move_to(10, 10 + lineHeight);
str8 text = str8_pushf(mem_scratch(),
"Milepost vector graphics test program (frame time = %fs, fps = %f)...",
@ -185,6 +186,8 @@ int main()
mg_text_outlines(text);
mg_fill();
f64 startFlushTime = mp_get_time(MP_CLOCK_MONOTONIC);
mg_flush();
f64 startPresentTime = mp_get_time(MP_CLOCK_MONOTONIC);

View File

@ -1,3 +1,3 @@
set INCLUDES=/I ..\..\src /I ..\..\src\util /I ..\..\src\platform /I ../../ext /I ../../ext/angle_headers
cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GL /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib user32.lib opengl32.lib gdi32.lib /out:../../bin/example_canvas.exe
cl /we4013 /Zi /Zc:preprocessor /DMG_IMPLEMENTS_BACKEND_GL /std:c11 %INCLUDES% main.c /link /LIBPATH:../../bin milepost.lib user32.lib opengl32.lib gdi32.lib shcore.lib /out:../../bin/example_canvas.exe

View File

@ -181,10 +181,11 @@ void mg_gl_canvas_draw_batch(mg_canvas_backend* interface, u32 shapeCount, u32 v
mg_gl_send_buffers(backend, shapeCount, vertexCount, indexCount);
mp_rect frame = mg_surface_get_frame(backend->surface);
vec2 contentsScaling = mg_surface_contents_scaling(backend->surface);
const int tileSize = 16;
const int tileCountX = (frame.w + tileSize - 1)/tileSize;
const int tileCountY = (frame.h + tileSize - 1)/tileSize;
const int tileCountX = (frame.w*contentsScaling.x + tileSize - 1)/tileSize;
const int tileCountY = (frame.h*contentsScaling.y + tileSize - 1)/tileSize;
const int tileArraySize = MG_GL_CANVAS_TILE_ARRAY_SIZE;
//TODO: ensure there's enough space in tile buffer
@ -208,6 +209,7 @@ void mg_gl_canvas_draw_batch(mg_canvas_backend* interface, u32 shapeCount, u32 v
glUniform2ui(1, tileCountX, tileCountY);
glUniform1ui(2, tileSize);
glUniform1ui(3, tileArraySize);
glUniform2f(4, contentsScaling.x, contentsScaling.y);
u32 threadCount = indexCount/3;
glDispatchCompute((threadCount + 255)/256, 1, 1);
@ -222,7 +224,7 @@ void mg_gl_canvas_draw_batch(mg_canvas_backend* interface, u32 shapeCount, u32 v
glDispatchCompute(tileCountX * tileCountY, 1, 1);
//NOTE: then we fire the fragment shader that will select only triangles in its tile
//NOTE: then we fire the drawing shader that will select only triangles in its tile
glUseProgram(backend->drawProgram);
glBindImageTexture(0, backend->outTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
@ -231,6 +233,7 @@ void mg_gl_canvas_draw_batch(mg_canvas_backend* interface, u32 shapeCount, u32 v
glUniform2ui(1, tileCountX, tileCountY);
glUniform1ui(2, tileSize);
glUniform1ui(3, tileArraySize);
glUniform2f(4, contentsScaling.x, contentsScaling.y);
glDispatchCompute(tileCountX, tileCountY, 1);
@ -330,9 +333,11 @@ mg_canvas_backend* mg_gl_canvas_create(mg_surface surface)
glBufferData(GL_SHADER_STORAGE_BUFFER, MG_GL_CANVAS_TILE_ARRAY_BUFFER_SIZE, 0, GL_DYNAMIC_COPY);
mp_rect frame = mg_surface_get_frame(backend->surface);
vec2 contentsScaling = mg_surface_contents_scaling(backend->surface);
glGenTextures(1, &backend->outTexture);
glBindTexture(GL_TEXTURE_2D, backend->outTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, frame.w, frame.h);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, frame.w*contentsScaling.x, frame.h*contentsScaling.y);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

View File

@ -42,8 +42,9 @@ layout(location = 0) uniform uint indexCount;
layout(location = 1) uniform uvec2 tileCount;
layout(location = 2) uniform uint tileSize;
layout(location = 3) uniform uint tileArraySize;
layout(rgba8, binding = 0) uniform restrict writeonly image2D outTexture;
layout(location = 4) uniform vec2 scaling;
layout(rgba8, binding = 0) uniform restrict writeonly image2D outTexture;
bool is_top_left(ivec2 a, ivec2 b)
{
@ -63,25 +64,25 @@ void main()
uint tileIndex = tileCoord.y * tileCount.x + tileCoord.x;
uint tileCounter = tileCounterBuffer.elements[tileIndex];
const float subPixelFactor = 16.;
const float subPixelFactor = 256.;
ivec2 centerPoint = ivec2((vec2(pixelCoord) + vec2(0.5, 0.5)) * subPixelFactor);
//*
const int sampleCount = 8;
ivec2 samplePoints[sampleCount] = ivec2[sampleCount](centerPoint + ivec2(1, 3),
centerPoint + ivec2(-1, -3),
centerPoint + ivec2(5, -1),
centerPoint + ivec2(-3, 5),
centerPoint + ivec2(-5, -5),
centerPoint + ivec2(-7, 1),
centerPoint + ivec2(3, -7),
centerPoint + ivec2(7, 7));
ivec2 samplePoints[sampleCount] = ivec2[sampleCount](centerPoint + ivec2(1, 3)*16,
centerPoint + ivec2(-1, -3)*16,
centerPoint + ivec2(5, -1)*16,
centerPoint + ivec2(-3, 5)*16,
centerPoint + ivec2(-5, -5)*16,
centerPoint + ivec2(-7, 1)*16,
centerPoint + ivec2(3, -7)*16,
centerPoint + ivec2(7, 7)*16);
/*/
const int sampleCount = 4;
ivec2 samplePoints[sampleCount] = ivec2[sampleCount](centerPoint + ivec2(-2, 6),
centerPoint + ivec2(6, 2),
centerPoint + ivec2(-6, -2),
centerPoint + ivec2(2, -6));
ivec2 samplePoints[sampleCount] = ivec2[sampleCount](centerPoint + ivec2(-2, 6)*16,
centerPoint + ivec2(6, 2)*16,
centerPoint + ivec2(-6, -2)*16,
centerPoint + ivec2(2, -6)*16);
//*/
//DEBUG
/*
@ -132,13 +133,13 @@ void main()
uint i1 = indexBuffer.elements[triangleIndex+1u];
uint i2 = indexBuffer.elements[triangleIndex+2u];
ivec2 p0 = ivec2((vertexBuffer.elements[i0].pos) * subPixelFactor);
ivec2 p1 = ivec2((vertexBuffer.elements[i1].pos) * subPixelFactor);
ivec2 p2 = ivec2((vertexBuffer.elements[i2].pos) * subPixelFactor);
ivec2 p0 = ivec2((vertexBuffer.elements[i0].pos * scaling) * subPixelFactor);
ivec2 p1 = ivec2((vertexBuffer.elements[i1].pos * scaling) * subPixelFactor);
ivec2 p2 = ivec2((vertexBuffer.elements[i2].pos * scaling) * subPixelFactor);
int zIndex = vertexBuffer.elements[i0].zIndex;
vec4 color = shapeBuffer.elements[zIndex].color;
ivec4 clip = ivec4(round((shapeBuffer.elements[zIndex].clip + vec4(0.5, 0.5, 0.5, 0.5)) * subPixelFactor));
ivec4 clip = ivec4(round((shapeBuffer.elements[zIndex].clip * vec4(scaling, scaling) + vec4(0.5, 0.5, 0.5, 0.5)) * subPixelFactor));
//NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge
int cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x;

View File

@ -40,6 +40,7 @@ layout(location = 0) uniform uint indexCount;
layout(location = 1) uniform uvec2 tileCount;
layout(location = 2) uniform uint tileSize;
layout(location = 3) uniform uint tileArraySize;
layout(location = 4) uniform vec2 scaling;
void main()
{
@ -53,12 +54,12 @@ void main()
uint i1 = indexBuffer.elements[triangleIndex+1u];
uint i2 = indexBuffer.elements[triangleIndex+2u];
vec2 p0 = vertexBuffer.elements[i0].pos;
vec2 p1 = vertexBuffer.elements[i1].pos;
vec2 p2 = vertexBuffer.elements[i2].pos;
vec2 p0 = vertexBuffer.elements[i0].pos * scaling;
vec2 p1 = vertexBuffer.elements[i1].pos * scaling;
vec2 p2 = vertexBuffer.elements[i2].pos * scaling;
int shapeIndex = vertexBuffer.elements[i0].zIndex;
vec4 clip = shapeBuffer.elements[shapeIndex].clip;
vec4 clip = shapeBuffer.elements[shapeIndex].clip * vec4(scaling, scaling);
vec4 fbox = vec4(max(min(min(p0.x, p1.x), p2.x), clip.x),
max(min(min(p0.y, p1.y), p2.y), clip.y),

View File

@ -331,6 +331,19 @@ void mg_surface_swap_interval(mg_surface surface, int swap)
}
}
vec2 mg_surface_contents_scaling(mg_surface surface)
{
DEBUG_ASSERT(__mgData.init);
vec2 scaling = {1, 1};
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData)
{
scaling = surfaceData->contentsScaling(surfaceData);
}
return(scaling);
}
void mg_surface_set_frame(mg_surface surface, mp_rect frame)
{
DEBUG_ASSERT(__mgData.init);

View File

@ -21,6 +21,7 @@ void mg_surface_destroy(mg_surface surface);
void mg_surface_prepare(mg_surface surface);
void mg_surface_present(mg_surface surface);
void mg_surface_swap_interval(mg_surface surface, int swap);
vec2 mg_surface_contents_scaling(mg_surface surface);
mp_rect mg_surface_get_frame(mg_surface surface);
void mg_surface_set_frame(mg_surface surface, mp_rect frame);
bool mg_surface_get_hidden(mg_surface surface);

View File

@ -28,6 +28,7 @@ typedef void (*mg_surface_destroy_proc)(mg_surface_data* surface);
typedef void (*mg_surface_prepare_proc)(mg_surface_data* surface);
typedef void (*mg_surface_present_proc)(mg_surface_data* surface);
typedef void (*mg_surface_swap_interval_proc)(mg_surface_data* surface, int swap);
typedef vec2 (*mg_surface_contents_scaling_proc)(mg_surface_data* surface);
typedef mp_rect (*mg_surface_get_frame_proc)(mg_surface_data* surface);
typedef void (*mg_surface_set_frame_proc)(mg_surface_data* surface, mp_rect frame);
typedef bool (*mg_surface_get_hidden_proc)(mg_surface_data* surface);
@ -41,6 +42,7 @@ typedef struct mg_surface_data
mg_surface_prepare_proc prepare;
mg_surface_present_proc present;
mg_surface_swap_interval_proc swapInterval;
mg_surface_contents_scaling_proc contentsScaling;
mg_surface_get_frame_proc getFrame;
mg_surface_set_frame_proc setFrame;
mg_surface_get_hidden_proc getHidden;

View File

@ -156,6 +156,8 @@ void mp_init()
__mpApp.win32.savedConsoleCodePage = GetConsoleOutputCP();
SetConsoleOutputCP(CP_UTF8);
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
}
}
@ -258,6 +260,12 @@ LRESULT WinProc(HWND windowHandle, UINT message, WPARAM wParam, LPARAM lParam)
mp_queue_event(&event);
} break;
case WM_DPICHANGED:
{
printf("DPI changed!\n");
} break;
//TODO: enter/exit size & move
case WM_SIZING:
@ -502,6 +510,12 @@ void mp_pump_events(f64 timeout)
// window management
//--------------------------------------------------------------------
//WARN: the following header pulls in objbase.h (even with WIN32_LEAN_AND_MEAN), which
// #defines interface to struct... so make sure to #undef interface since it's a
// name we want to be able to use throughout the codebase
#include<ShellScalingApi.h>
#undef interface
mp_window mp_window_create(mp_rect rect, const char* title, mp_window_style style)
{
WNDCLASS windowClass = {.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
@ -516,19 +530,16 @@ mp_window mp_window_create(mp_rect rect, const char* title, mp_window_style styl
goto quit;
}
/*
//NOTE: get primary monitor dimensions
const POINT ptZero = { 0, 0 };
HMONITOR monitor = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorInfo = {.cbSize = sizeof(MONITORINFO)};
GetMonitorInfo(monitor, &monitorInfo);
RECT adjustRect = {rect.x, monitorInfo.rcMonitor.bottom - rect.y - rect.h, rect.w, rect.h};
AdjustWindowRect(&adjustRect, WS_OVERLAPPEDWINDOW, false);
*/
u32 dpiX, dpiY;
HMONITOR monitor = MonitorFromPoint((POINT){rect.x, rect.y}, MONITOR_DEFAULTTOPRIMARY);
GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
f32 dpiScalingX = (f32)dpiX/96.;
f32 dpiScalingY = (f32)dpiY/96.;
HWND windowHandle = CreateWindow("ApplicationWindowClass", "Test Window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
rect.w, rect.h,
rect.w * dpiScalingX, rect.h * dpiScalingY,
0, 0, windowClass.hInstance, 0);
if(!windowHandle)
@ -740,7 +751,9 @@ mp_rect mp_window_get_content_rect(mp_window window)
RECT winRect;
if(GetClientRect(windowData->win32.hWnd, &winRect))
{
rect = (mp_rect){0, 0, winRect.right - winRect.left, winRect.bottom - winRect.top};
u32 dpi = GetDpiForWindow(windowData->win32.hWnd);
f32 scale = (float)dpi/96.;
rect = (mp_rect){0, 0, (winRect.right - winRect.left)/scale, (winRect.bottom - winRect.top)/scale};
}
}
return(rect);

View File

@ -1,36 +1,37 @@
//*****************************************************************
//
// $file: win32_app.h $
// $author: Martin Fouilleul $
// $date: 20/12/2022 $
// $revision: $
// $note: (C) 2022 by Martin Fouilleul - all rights reserved $
//
//*****************************************************************
#ifndef __WIN32_APP_H_
#define __WIN32_APP_H_
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#include"mp_app.h"
typedef struct win32_window_data
{
HWND hWnd;
} win32_window_data;
#define MP_PLATFORM_WINDOW_DATA win32_window_data win32;
typedef struct win32_app_data
{
u32 savedConsoleCodePage;
int mouseCaptureMask;
bool mouseTracked;
} win32_app_data;
#define MP_PLATFORM_APP_DATA win32_app_data win32;
#endif __WIN32_APP_H_
//*****************************************************************
//
// $file: win32_app.h $
// $author: Martin Fouilleul $
// $date: 20/12/2022 $
// $revision: $
// $note: (C) 2022 by Martin Fouilleul - all rights reserved $
//
//*****************************************************************
#ifndef __WIN32_APP_H_
#define __WIN32_APP_H_
#include"mp_app.h"
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
typedef struct win32_window_data
{
HWND hWnd;
} win32_window_data;
#define MP_PLATFORM_WINDOW_DATA win32_window_data win32;
typedef struct win32_app_data
{
u32 savedConsoleCodePage;
int mouseCaptureMask;
bool mouseTracked;
} win32_app_data;
#define MP_PLATFORM_APP_DATA win32_app_data win32;
#endif __WIN32_APP_H_

View File

@ -44,6 +44,8 @@
GL_PROC(GLDISPATCHCOMPUTE, glDispatchCompute) \
GL_PROC(GLUNIFORM1UI, glUniform1ui) \
GL_PROC(GLUNIFORM2UI, glUniform2ui) \
GL_PROC(GLUNIFORM1F, glUniform1f) \
GL_PROC(GLUNIFORM2F, glUniform2f) \
GL_PROC(GLBINDIMAGETEXTURE, glBindImageTexture) \
GL_PROC(GLACTIVETEXTURE, glActiveTexture) \
GL_PROC(GLUNIFORM1I, glUniform1i) \

View File

@ -6,12 +6,11 @@
* @revision:
*
*****************************************************************/
#define WIN32_GL_LOADER_IMPL
#include"win32_gl_loader.h"
#include"win32_app.h"
#include"graphics_internal.h"
#include"win32_app.h"
typedef struct mg_gl_surface
{
@ -20,6 +19,7 @@ typedef struct mg_gl_surface
HWND hWnd;
HDC hDC;
HGLRC glContext;
vec2 contentsScaling;
} mg_gl_surface;
@ -48,13 +48,24 @@ void mg_gl_surface_swap_interval(mg_surface_data* interface, int swap)
wglSwapIntervalEXT(swap);
}
vec2 mg_gl_contents_scaling(mg_surface_data* interface)
{
mg_gl_surface* surface = (mg_gl_surface*)interface;
return(surface->contentsScaling);
}
mp_rect mg_gl_surface_get_frame(mg_surface_data* interface)
{
mg_gl_surface* surface = (mg_gl_surface*)interface;
RECT rect = {0};
GetClientRect(surface->hWnd, &rect);
mp_rect res = {rect.left, rect.bottom, rect.right - rect.left, rect.bottom - rect.top};
vec2 scale = surface->contentsScaling;
mp_rect res = {rect.left/scale.x,
rect.bottom/scale.y,
(rect.right - rect.left)/scale.x,
(rect.bottom - rect.top)/scale.y};
return(res);
}
@ -200,6 +211,7 @@ mg_surface mg_gl_surface_create_for_window(mp_window window)
surface->interface.prepare = mg_gl_surface_prepare;
surface->interface.present = mg_gl_surface_present;
surface->interface.swapInterval = mg_gl_surface_swap_interval;
surface->interface.contentsScaling = mg_gl_contents_scaling;
surface->interface.getFrame = mg_gl_surface_get_frame;
//TODO: get/set frame/hidden
@ -207,6 +219,9 @@ mg_surface mg_gl_surface_create_for_window(mp_window window)
surface->hDC = hDC;
surface->glContext = glContext;
u32 dpi = GetDpiForWindow(windowData->win32.hWnd);
surface->contentsScaling = (vec2){(float)dpi/96., (float)dpi/96.};
surfaceHandle = mg_surface_alloc_handle((mg_surface_data*)surface);
}

View File

@ -10,7 +10,7 @@ Canvas renderer perf
by subpixel precision and truncating... -> ie sampling at the middle of pixels vs at integer coordinates...
[ ] What alignment gives crisp-ier lines?
[>] Add surface scaling for high dpi surfaces
[>] Add surface scaling for high dpi surfaces and check that our fonts are renderer smoothly
[x] Allow setting swap interval
[!] Allow swap interval of 0 on macos