feat(windows): a window sized by the display's scale, as a Retina Mac does
A game asking for 960 x 540 got 960 x 540 pixels - a quarter of a 4K panel. w_fit_scale turns the display's DPI into a whole-number scale (96 -> 1, 168 and 192 -> 2), brought down until the window fits; win_open and win_gl_resize size the client area with it, win_gl_scale reports it the way cocoa.ll reports the backing scale, and the mouse still arrives in the game's own units. Verified on a 3840x2160 panel at 175%: windowed gl_triangle's 640x360 window draws a 1280x720 frame. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3300fb3957
commit
63d9f61bf1
2 changed files with 79 additions and 15 deletions
|
|
@ -128,6 +128,43 @@ def:
|
||||||
ret i64 %r
|
ret i64 %r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare i32 @GetDpiForSystem()
|
||||||
|
declare i32 @GetDpiForWindow(ptr)
|
||||||
|
|
||||||
|
; The display's scale as a whole number, the way macOS reports a backing scale: 96 dpi
|
||||||
|
; is 1, 175% (168 dpi) and 200% are 2, 300% is 3 - rounded, then brought down until a
|
||||||
|
; client area of w x h at that scale fits on the primary display. Shared with
|
||||||
|
; win32_gl.ll. A window of "960 x 540" is then the same size on the desk on a 4K
|
||||||
|
; panel as on a 1080p one, which is what a game asking for 960 x 540 means.
|
||||||
|
define i32 @w_fit_scale(i32 %dpi, i32 %w, i32 %h) {
|
||||||
|
entry:
|
||||||
|
%d0 = icmp slt i32 %dpi, 96
|
||||||
|
%d = select i1 %d0, i32 96, i32 %dpi
|
||||||
|
%d48 = add i32 %d, 48
|
||||||
|
%s0 = sdiv i32 %d48, 96
|
||||||
|
%sw = call i32 @GetSystemMetrics(i32 0)
|
||||||
|
%sh = call i32 @GetSystemMetrics(i32 1)
|
||||||
|
br label %fit
|
||||||
|
fit:
|
||||||
|
%s = phi i32 [ %s0, %entry ], [ %s1, %shrink ]
|
||||||
|
%one = icmp sle i32 %s, 1
|
||||||
|
br i1 %one, label %done, label %test
|
||||||
|
test:
|
||||||
|
%cw = mul i32 %w, %s
|
||||||
|
%ch = mul i32 %h, %s
|
||||||
|
%wide = icmp sgt i32 %cw, %sw
|
||||||
|
%tall = icmp sgt i32 %ch, %sh
|
||||||
|
%over = or i1 %wide, %tall
|
||||||
|
br i1 %over, label %shrink, label %done
|
||||||
|
shrink:
|
||||||
|
%s1 = sub i32 %s, 1
|
||||||
|
br label %fit
|
||||||
|
done:
|
||||||
|
%r0 = icmp slt i32 %s, 1
|
||||||
|
%r = select i1 %r0, i32 1, i32 %s
|
||||||
|
ret i32 %r
|
||||||
|
}
|
||||||
|
|
||||||
define void @win_open(i32 %w, i32 %h, i32 %scale, ptr %title) {
|
define void @win_open(i32 %w, i32 %h, i32 %scale, ptr %title) {
|
||||||
entry:
|
entry:
|
||||||
%wc = alloca [80 x i8], align 8
|
%wc = alloca [80 x i8], align 8
|
||||||
|
|
@ -142,6 +179,13 @@ entry:
|
||||||
make:
|
make:
|
||||||
; DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
|
; DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
|
||||||
%dpi = call i32 @SetProcessDpiAwarenessContext(ptr inttoptr (i64 -4 to ptr))
|
%dpi = call i32 @SetProcessDpiAwarenessContext(ptr inttoptr (i64 -4 to ptr))
|
||||||
|
; the framebuffer's own scale, times the display's
|
||||||
|
%sysdpi = call i32 @GetDpiForSystem()
|
||||||
|
%gw = mul i32 %w, %scale
|
||||||
|
%gh = mul i32 %h, %scale
|
||||||
|
%ds = call i32 @w_fit_scale(i32 %sysdpi, i32 %gw, i32 %gh)
|
||||||
|
%scale2 = mul i32 %scale, %ds
|
||||||
|
store i32 %scale2, ptr @W_scale
|
||||||
%inst = call ptr @GetModuleHandleA(ptr null)
|
%inst = call ptr @GetModuleHandleA(ptr null)
|
||||||
; WNDCLASSEXA: cbSize 0, style 4, lpfnWndProc 8, hInstance 24, hCursor 40, lpszClassName 64
|
; WNDCLASSEXA: cbSize 0, style 4, lpfnWndProc 8, hInstance 24, hCursor 40, lpszClassName 64
|
||||||
call void @llvm.memset.p0.i64(ptr %wc, i8 0, i64 80, i1 false)
|
call void @llvm.memset.p0.i64(ptr %wc, i8 0, i64 80, i1 false)
|
||||||
|
|
@ -158,9 +202,9 @@ make:
|
||||||
%wcn = getelementptr i8, ptr %wc, i64 64
|
%wcn = getelementptr i8, ptr %wc, i64 64
|
||||||
store ptr @.w_class, ptr %wcn
|
store ptr @.w_class, ptr %wcn
|
||||||
%atom = call i16 @RegisterClassExA(ptr %wc)
|
%atom = call i16 @RegisterClassExA(ptr %wc)
|
||||||
; the client area is w*scale x h*scale pixels; the frame goes round it
|
; the client area is w x h at both scales; the frame goes round it
|
||||||
%pw = mul i32 %w, %scale
|
%pw = mul i32 %gw, %ds
|
||||||
%ph = mul i32 %h, %scale
|
%ph = mul i32 %gh, %ds
|
||||||
call void @llvm.memset.p0.i64(ptr %rc, i8 0, i64 16, i1 false)
|
call void @llvm.memset.p0.i64(ptr %rc, i8 0, i64 16, i1 false)
|
||||||
%rcr = getelementptr i8, ptr %rc, i64 8
|
%rcr = getelementptr i8, ptr %rc, i64 8
|
||||||
store i32 %pw, ptr %rcr
|
store i32 %pw, ptr %rcr
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,16 @@
|
||||||
; entry-point table). A headless build links gl_win_nowin.ll in its place.
|
; entry-point table). A headless build links gl_win_nowin.ll in its place.
|
||||||
;
|
;
|
||||||
; win_gl_attach() -> ok the context on the window's DC (0 = no window yet)
|
; win_gl_attach() -> ok the context on the window's DC (0 = no window yet)
|
||||||
; win_gl_resize(w, h) client area of w x h pixels, centred
|
; win_gl_resize(w, h) a client area of w x h at the display's scale, centred
|
||||||
; win_gl_swap() SwapBuffers, paced by the swap interval
|
; win_gl_swap() SwapBuffers, paced by the swap interval
|
||||||
; win_gl_scale() -> 1 the process is DPI aware: one pixel is one pixel
|
; win_gl_scale() -> int pixels per window unit: 1 at 100%, 2 at 175-200%, ...
|
||||||
; win_gl_drawable(out) the client area in pixels
|
; win_gl_drawable(out) the client area in pixels
|
||||||
; win_toggle_fullscreen() borderless, over the monitor the window is on
|
; win_toggle_fullscreen() borderless, over the monitor the window is on
|
||||||
|
;
|
||||||
|
; The units follow cocoa.ll's points and backing pixels: a game asks for a 960 x 540
|
||||||
|
; window, the display's whole-number scale (w_fit_scale, win32.ll) turns that into
|
||||||
|
; pixels, win_gl_scale reports the factor as the Retina backing scale is reported,
|
||||||
|
; and the mouse keeps arriving in the game's own units because W_scale divides by it.
|
||||||
; ============================================================================
|
; ============================================================================
|
||||||
|
|
||||||
@W_hwnd = external global ptr
|
@W_hwnd = external global ptr
|
||||||
|
|
@ -19,6 +24,7 @@
|
||||||
@W_fbh = external global i32
|
@W_fbh = external global i32
|
||||||
@W_scale = external global i32
|
@W_scale = external global i32
|
||||||
|
|
||||||
|
declare i32 @w_fit_scale(i32, i32, i32)
|
||||||
declare ptr @lgl_wgl_core(ptr, i32)
|
declare ptr @lgl_wgl_core(ptr, i32)
|
||||||
declare ptr @wglGetProcAddress(ptr)
|
declare ptr @wglGetProcAddress(ptr)
|
||||||
declare i32 @SwapBuffers(ptr)
|
declare i32 @SwapBuffers(ptr)
|
||||||
|
|
@ -26,6 +32,7 @@ declare i32 @GetClientRect(ptr, ptr)
|
||||||
declare i32 @AdjustWindowRectEx(ptr, i32, i32, i32)
|
declare i32 @AdjustWindowRectEx(ptr, i32, i32, i32)
|
||||||
declare i32 @SetWindowPos(ptr, ptr, i32, i32, i32, i32, i32)
|
declare i32 @SetWindowPos(ptr, ptr, i32, i32, i32, i32, i32)
|
||||||
declare i32 @GetSystemMetrics(i32)
|
declare i32 @GetSystemMetrics(i32)
|
||||||
|
declare i32 @GetDpiForWindow(ptr)
|
||||||
declare i64 @GetWindowLongPtrA(ptr, i32)
|
declare i64 @GetWindowLongPtrA(ptr, i32)
|
||||||
declare i64 @SetWindowLongPtrA(ptr, i32, i64)
|
declare i64 @SetWindowLongPtrA(ptr, i32, i64)
|
||||||
declare i32 @GetWindowRect(ptr, ptr)
|
declare i32 @GetWindowRect(ptr, ptr)
|
||||||
|
|
@ -36,6 +43,7 @@ declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
|
||||||
@.wgl_swapint = private unnamed_addr constant [19 x i8] c"wglSwapIntervalEXT\00"
|
@.wgl_swapint = private unnamed_addr constant [19 x i8] c"wglSwapIntervalEXT\00"
|
||||||
|
|
||||||
@W_glctx = internal global ptr null
|
@W_glctx = internal global ptr null
|
||||||
|
@W_glscale = internal global i32 1
|
||||||
@W_fs = internal global i32 0
|
@W_fs = internal global i32 0
|
||||||
@W_fs_style = internal global i64 0
|
@W_fs_style = internal global i64 0
|
||||||
@W_fs_rect = internal global [4 x i32] zeroinitializer
|
@W_fs_rect = internal global [4 x i32] zeroinitializer
|
||||||
|
|
@ -71,18 +79,23 @@ entry:
|
||||||
%nowin = icmp eq ptr %hwnd, null
|
%nowin = icmp eq ptr %hwnd, null
|
||||||
br i1 %nowin, label %out, label %go
|
br i1 %nowin, label %out, label %go
|
||||||
go:
|
go:
|
||||||
|
%dpi = call i32 @GetDpiForWindow(ptr %hwnd)
|
||||||
|
%s = call i32 @w_fit_scale(i32 %dpi, i32 %w, i32 %h)
|
||||||
|
store i32 %s, ptr @W_glscale
|
||||||
store i32 %w, ptr @W_fbw
|
store i32 %w, ptr @W_fbw
|
||||||
store i32 %h, ptr @W_fbh
|
store i32 %h, ptr @W_fbh
|
||||||
store i32 1, ptr @W_scale
|
store i32 %s, ptr @W_scale
|
||||||
%fs = load i32, ptr @W_fs
|
%fs = load i32, ptr @W_fs
|
||||||
%isfs = icmp ne i32 %fs, 0
|
%isfs = icmp ne i32 %fs, 0
|
||||||
br i1 %isfs, label %out, label %size
|
br i1 %isfs, label %out, label %size
|
||||||
size:
|
size:
|
||||||
|
%pw = mul i32 %w, %s
|
||||||
|
%ph = mul i32 %h, %s
|
||||||
call void @llvm.memset.p0.i64(ptr %rc, i8 0, i64 16, i1 false)
|
call void @llvm.memset.p0.i64(ptr %rc, i8 0, i64 16, i1 false)
|
||||||
%rcr = getelementptr i8, ptr %rc, i64 8
|
%rcr = getelementptr i8, ptr %rc, i64 8
|
||||||
store i32 %w, ptr %rcr
|
store i32 %pw, ptr %rcr
|
||||||
%rcb = getelementptr i8, ptr %rc, i64 12
|
%rcb = getelementptr i8, ptr %rc, i64 12
|
||||||
store i32 %h, ptr %rcb
|
store i32 %ph, ptr %rcb
|
||||||
%style64 = call i64 @GetWindowLongPtrA(ptr %hwnd, i32 -16) ; GWL_STYLE
|
%style64 = call i64 @GetWindowLongPtrA(ptr %hwnd, i32 -16) ; GWL_STYLE
|
||||||
%style = trunc i64 %style64 to i32
|
%style = trunc i64 %style64 to i32
|
||||||
%adj = call i32 @AdjustWindowRectEx(ptr %rc, i32 %style, i32 0, i32 0)
|
%adj = call i32 @AdjustWindowRectEx(ptr %rc, i32 %style, i32 0, i32 0)
|
||||||
|
|
@ -124,7 +137,8 @@ out:
|
||||||
|
|
||||||
define i32 @win_gl_scale() {
|
define i32 @win_gl_scale() {
|
||||||
entry:
|
entry:
|
||||||
ret i32 1
|
%s = load i32, ptr @W_glscale
|
||||||
|
ret i32 %s
|
||||||
}
|
}
|
||||||
|
|
||||||
; 1 = vsync (set at attach), 0 = free-running; wglSwapIntervalEXT is a driver extension,
|
; 1 = vsync (set at attach), 0 = free-running; wglSwapIntervalEXT is a driver extension,
|
||||||
|
|
@ -147,6 +161,8 @@ out:
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; the client area in pixels -> out[0], out[1]; and the window size in the game's units,
|
||||||
|
; which the mouse mapping divides by, kept in step with a window the player resized
|
||||||
define void @win_gl_drawable(ptr %out) {
|
define void @win_gl_drawable(ptr %out) {
|
||||||
entry:
|
entry:
|
||||||
%rc = alloca [16 x i8], align 4
|
%rc = alloca [16 x i8], align 4
|
||||||
|
|
@ -162,14 +178,18 @@ go:
|
||||||
store i32 %w, ptr %out
|
store i32 %w, ptr %out
|
||||||
%o1 = getelementptr i32, ptr %out, i32 1
|
%o1 = getelementptr i32, ptr %out, i32 1
|
||||||
store i32 %h, ptr %o1
|
store i32 %h, ptr %o1
|
||||||
; a minimised window has no client area; keep the last real size for the mouse mapping
|
; a minimised window has no client area; keep the last real size
|
||||||
%zw = icmp sle i32 %w, 0
|
%zw = icmp sle i32 %w, 0
|
||||||
%zh = icmp sle i32 %h, 0
|
%zh = icmp sle i32 %h, 0
|
||||||
%gone = or i1 %zw, %zh
|
%gone = or i1 %zw, %zh
|
||||||
br i1 %gone, label %done, label %track
|
br i1 %gone, label %done, label %track
|
||||||
track:
|
track:
|
||||||
store i32 %w, ptr @W_fbw
|
%s = load i32, ptr @W_glscale
|
||||||
store i32 %h, ptr @W_fbh
|
%uw = sdiv i32 %w, %s
|
||||||
|
%uh = sdiv i32 %h, %s
|
||||||
|
store i32 %uw, ptr @W_fbw
|
||||||
|
store i32 %uh, ptr @W_fbh
|
||||||
|
store i32 %s, ptr @W_scale
|
||||||
br label %done
|
br label %done
|
||||||
done:
|
done:
|
||||||
ret void
|
ret void
|
||||||
|
|
@ -186,9 +206,9 @@ entry:
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
; The Retina switch: on Windows the process is DPI aware and always draws one pixel per
|
; The Retina switch. The process is DPI aware and always draws one pixel per screen
|
||||||
; screen pixel, which is what "on" means on macOS; there is no lower-resolution surface
|
; pixel, which is what "on" means on macOS; there is no lower-resolution surface for
|
||||||
; for "off" to fall back to, so it changes nothing.
|
; "off" to fall back to, so it changes nothing here.
|
||||||
define void @win_gl_retina(i32 %on) {
|
define void @win_gl_retina(i32 %on) {
|
||||||
entry:
|
entry:
|
||||||
ret void
|
ret void
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue