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>
273 lines
9 KiB
LLVM
273 lines
9 KiB
LLVM
; ============================================================================
|
|
; win32_gl.ll — OpenGL on the Windows window (cocoa.ll's win_gl_* half).
|
|
;
|
|
; Linked into a windowed Windows program that uses Gl.*, beside win32.ll (the
|
|
; window) and gl_win.ll (which makes a 4.1 core context on a DC and fills the
|
|
; 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_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_scale() -> int pixels per window unit: 1 at 100%, 2 at 175-200%, ...
|
|
; win_gl_drawable(out) the client area in pixels
|
|
; 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_hdc = external global ptr
|
|
@W_fbw = external global i32
|
|
@W_fbh = 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 @wglGetProcAddress(ptr)
|
|
declare i32 @SwapBuffers(ptr)
|
|
declare i32 @GetClientRect(ptr, ptr)
|
|
declare i32 @AdjustWindowRectEx(ptr, i32, i32, i32)
|
|
declare i32 @SetWindowPos(ptr, ptr, i32, i32, i32, i32, i32)
|
|
declare i32 @GetSystemMetrics(i32)
|
|
declare i32 @GetDpiForWindow(ptr)
|
|
declare i64 @GetWindowLongPtrA(ptr, i32)
|
|
declare i64 @SetWindowLongPtrA(ptr, i32, i64)
|
|
declare i32 @GetWindowRect(ptr, ptr)
|
|
declare ptr @MonitorFromWindow(ptr, i32)
|
|
declare i32 @GetMonitorInfoA(ptr, ptr)
|
|
declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
|
|
|
|
@.wgl_swapint = private unnamed_addr constant [19 x i8] c"wglSwapIntervalEXT\00"
|
|
|
|
@W_glctx = internal global ptr null
|
|
@W_glscale = internal global i32 1
|
|
@W_fs = internal global i32 0
|
|
@W_fs_style = internal global i64 0
|
|
@W_fs_rect = internal global [4 x i32] zeroinitializer
|
|
|
|
define i32 @win_gl_attach() {
|
|
entry:
|
|
%have = load ptr, ptr @W_glctx
|
|
%again = icmp ne ptr %have, null
|
|
br i1 %again, label %ok, label %make
|
|
make:
|
|
%hdc = load ptr, ptr @W_hdc
|
|
%nodc = icmp eq ptr %hdc, null
|
|
br i1 %nodc, label %fail, label %ctx
|
|
ctx:
|
|
; 24-bit colour with 8 of alpha, 24 depth, 8 stencil, double-buffered: cocoa.ll's format
|
|
%c = call ptr @lgl_wgl_core(ptr %hdc, i32 8)
|
|
%noc = icmp eq ptr %c, null
|
|
br i1 %noc, label %fail, label %keep
|
|
keep:
|
|
store ptr %c, ptr @W_glctx
|
|
call void @win_gl_swap_interval(i32 1)
|
|
br label %ok
|
|
ok:
|
|
ret i32 1
|
|
fail:
|
|
ret i32 0
|
|
}
|
|
|
|
define void @win_gl_resize(i32 %w, i32 %h) {
|
|
entry:
|
|
%rc = alloca [16 x i8], align 4
|
|
%hwnd = load ptr, ptr @W_hwnd
|
|
%nowin = icmp eq ptr %hwnd, null
|
|
br i1 %nowin, label %out, label %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 %h, ptr @W_fbh
|
|
store i32 %s, ptr @W_scale
|
|
%fs = load i32, ptr @W_fs
|
|
%isfs = icmp ne i32 %fs, 0
|
|
br i1 %isfs, label %out, label %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)
|
|
%rcr = getelementptr i8, ptr %rc, i64 8
|
|
store i32 %pw, ptr %rcr
|
|
%rcb = getelementptr i8, ptr %rc, i64 12
|
|
store i32 %ph, ptr %rcb
|
|
%style64 = call i64 @GetWindowLongPtrA(ptr %hwnd, i32 -16) ; GWL_STYLE
|
|
%style = trunc i64 %style64 to i32
|
|
%adj = call i32 @AdjustWindowRectEx(ptr %rc, i32 %style, i32 0, i32 0)
|
|
%l = load i32, ptr %rc
|
|
%tp = getelementptr i8, ptr %rc, i64 4
|
|
%t = load i32, ptr %tp
|
|
%r = load i32, ptr %rcr
|
|
%b = load i32, ptr %rcb
|
|
%ww = sub i32 %r, %l
|
|
%wh = sub i32 %b, %t
|
|
%sw = call i32 @GetSystemMetrics(i32 0)
|
|
%sh = call i32 @GetSystemMetrics(i32 1)
|
|
%dx = sub i32 %sw, %ww
|
|
%x0 = sdiv i32 %dx, 2
|
|
%xneg = icmp slt i32 %x0, 0
|
|
%x = select i1 %xneg, i32 0, i32 %x0
|
|
%dy = sub i32 %sh, %wh
|
|
%y0 = sdiv i32 %dy, 2
|
|
%yneg = icmp slt i32 %y0, 0
|
|
%y = select i1 %yneg, i32 0, i32 %y0
|
|
%sp = call i32 @SetWindowPos(ptr %hwnd, ptr null, i32 %x, i32 %y, i32 %ww, i32 %wh, i32 20) ; SWP_NOZORDER | SWP_NOACTIVATE
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
define void @win_gl_swap() {
|
|
entry:
|
|
%c = load ptr, ptr @W_glctx
|
|
%noc = icmp eq ptr %c, null
|
|
br i1 %noc, label %out, label %go
|
|
go:
|
|
%hdc = load ptr, ptr @W_hdc
|
|
%r = call i32 @SwapBuffers(ptr %hdc)
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
define i32 @win_gl_scale() {
|
|
entry:
|
|
%s = load i32, ptr @W_glscale
|
|
ret i32 %s
|
|
}
|
|
|
|
; 1 = vsync (set at attach), 0 = free-running; wglSwapIntervalEXT is a driver extension,
|
|
; reachable only through the current context
|
|
define void @win_gl_swap_interval(i32 %n) {
|
|
entry:
|
|
%c = load ptr, ptr @W_glctx
|
|
%noc = icmp eq ptr %c, null
|
|
br i1 %noc, label %out, label %find
|
|
find:
|
|
%fp = call ptr @wglGetProcAddress(ptr @.wgl_swapint)
|
|
%fpi = ptrtoint ptr %fp to i64
|
|
%fpa = add i64 %fpi, 1
|
|
%bad = icmp ult i64 %fpa, 5
|
|
br i1 %bad, label %out, label %call
|
|
call:
|
|
%r = call i32 %fp(i32 %n)
|
|
br label %out
|
|
out:
|
|
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) {
|
|
entry:
|
|
%rc = alloca [16 x i8], align 4
|
|
%hwnd = load ptr, ptr @W_hwnd
|
|
%nowin = icmp eq ptr %hwnd, null
|
|
br i1 %nowin, label %none, label %go
|
|
go:
|
|
%g = call i32 @GetClientRect(ptr %hwnd, ptr %rc)
|
|
%rp = getelementptr i8, ptr %rc, i64 8
|
|
%w = load i32, ptr %rp
|
|
%bp = getelementptr i8, ptr %rc, i64 12
|
|
%h = load i32, ptr %bp
|
|
store i32 %w, ptr %out
|
|
%o1 = getelementptr i32, ptr %out, i32 1
|
|
store i32 %h, ptr %o1
|
|
; a minimised window has no client area; keep the last real size
|
|
%zw = icmp sle i32 %w, 0
|
|
%zh = icmp sle i32 %h, 0
|
|
%gone = or i1 %zw, %zh
|
|
br i1 %gone, label %done, label %track
|
|
track:
|
|
%s = load i32, ptr @W_glscale
|
|
%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
|
|
done:
|
|
ret void
|
|
none:
|
|
store i32 0, ptr %out
|
|
%o1n = getelementptr i32, ptr %out, i32 1
|
|
store i32 0, ptr %o1n
|
|
ret void
|
|
}
|
|
|
|
; WGL needs no notice that its window changed size
|
|
define void @win_gl_update() {
|
|
entry:
|
|
ret void
|
|
}
|
|
|
|
; The Retina switch. The process is DPI aware and always draws one pixel per screen
|
|
; pixel, which is what "on" means on macOS; there is no lower-resolution surface for
|
|
; "off" to fall back to, so it changes nothing here.
|
|
define void @win_gl_retina(i32 %on) {
|
|
entry:
|
|
ret void
|
|
}
|
|
|
|
; Borderless full screen over the window's monitor, and back to the frame and the
|
|
; rectangle it had. (Exclusive full screen would change the display mode under a
|
|
; player's other windows; nothing in the game needs it.)
|
|
define void @win_toggle_fullscreen() {
|
|
entry:
|
|
%mi = alloca [40 x i8], align 4
|
|
%hwnd = load ptr, ptr @W_hwnd
|
|
%nowin = icmp eq ptr %hwnd, null
|
|
br i1 %nowin, label %out, label %which
|
|
which:
|
|
%fs = load i32, ptr @W_fs
|
|
%isfs = icmp ne i32 %fs, 0
|
|
br i1 %isfs, label %leave, label %enter
|
|
enter:
|
|
%style = call i64 @GetWindowLongPtrA(ptr %hwnd, i32 -16)
|
|
store i64 %style, ptr @W_fs_style
|
|
%gr = call i32 @GetWindowRect(ptr %hwnd, ptr @W_fs_rect)
|
|
%mon = call ptr @MonitorFromWindow(ptr %hwnd, i32 2) ; MONITOR_DEFAULTTONEAREST
|
|
call void @llvm.memset.p0.i64(ptr %mi, i8 0, i64 40, i1 false)
|
|
store i32 40, ptr %mi
|
|
%gm = call i32 @GetMonitorInfoA(ptr %mon, ptr %mi)
|
|
; MONITORINFO.rcMonitor at 4
|
|
%mlp = getelementptr i8, ptr %mi, i64 4
|
|
%ml = load i32, ptr %mlp
|
|
%mtp = getelementptr i8, ptr %mi, i64 8
|
|
%mt = load i32, ptr %mtp
|
|
%mrp = getelementptr i8, ptr %mi, i64 12
|
|
%mr = load i32, ptr %mrp
|
|
%mbp = getelementptr i8, ptr %mi, i64 16
|
|
%mb = load i32, ptr %mbp
|
|
; drop WS_OVERLAPPEDWINDOW (0x00CF0000), add WS_POPUP (0x80000000)
|
|
%bare = and i64 %style, -13565953
|
|
%popup = or i64 %bare, 2147483648
|
|
%sl = call i64 @SetWindowLongPtrA(ptr %hwnd, i32 -16, i64 %popup)
|
|
%fw = sub i32 %mr, %ml
|
|
%fh = sub i32 %mb, %mt
|
|
%sp = call i32 @SetWindowPos(ptr %hwnd, ptr null, i32 %ml, i32 %mt, i32 %fw, i32 %fh, i32 544) ; SWP_FRAMECHANGED | SWP_NOOWNERZORDER
|
|
store i32 1, ptr @W_fs
|
|
br label %out
|
|
leave:
|
|
%saved = load i64, ptr @W_fs_style
|
|
%sl2 = call i64 @SetWindowLongPtrA(ptr %hwnd, i32 -16, i64 %saved)
|
|
%l = load i32, ptr @W_fs_rect
|
|
%tp = getelementptr i32, ptr @W_fs_rect, i32 1
|
|
%t = load i32, ptr %tp
|
|
%rp = getelementptr i32, ptr @W_fs_rect, i32 2
|
|
%r = load i32, ptr %rp
|
|
%bp = getelementptr i32, ptr @W_fs_rect, i32 3
|
|
%b = load i32, ptr %bp
|
|
%ww = sub i32 %r, %l
|
|
%wh = sub i32 %b, %t
|
|
%sp2 = call i32 @SetWindowPos(ptr %hwnd, ptr null, i32 %l, i32 %t, i32 %ww, i32 %wh, i32 548) ; ... | SWP_NOZORDER
|
|
store i32 0, ptr @W_fs
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|