feat(windows): a window - win32.ll, WGL on it, and a windowed Windows build

win32.ll implements cocoa.ll's contracts over user32 and xinput: the message
pump, the held-key set and frame key in Ludic codes, the mouse with raw input
for cursor mode 2, clip-based cursor modes released on focus loss, XInput pads,
and the software framebuffer present. win32_gl.ll puts the WGL 4.1 core context
on that window (vsync, borderless full screen); gl_win.ll's context code is now
lgl_wgl_core, shared with the headless hidden window, whose win_gl_* stubs move
to gl_win_nowin.ll. audio_win.ll links Audio.* silently for now.

Verified: macOS fixpoint, ludic-dev test 135/135, selfhost-test 32/32; on the
PC gl_triangle renders identically headless and in a real window, and Maroon
Lake builds windowed and runs a playtest to frame 240 in the desktop session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-13 02:33:53 +03:00
parent 1bc255bc40
commit 00d25dcc3e
8 changed files with 1696 additions and 442 deletions

View file

@ -0,0 +1,36 @@
; ============================================================================
; audio_win.ll — the Windows sound backend (audio.ll's snd_* contract).
;
; Silent for now: every clip fails to load and every call is a no-op, which is the
; contract a headless build already runs on, so a windowed game links and plays
; without sound until the XAudio2 backend replaces this file.
; ============================================================================
define ptr @snd_load(ptr %path) {
entry:
ret ptr null
}
define void @snd_play(ptr %p, i32 %loops, i32 %rate, i32 %vol) {
entry:
ret void
}
define void @snd_stop(ptr %p) {
entry:
ret void
}
define i32 @snd_playing(ptr %p) {
entry:
ret i32 0
}
define void @snd_set_volume(ptr %p, i32 %vol) {
entry:
ret void
}
define void @snd_set_rate(ptr %p, i32 %rate) {
entry:
ret void
}
define void @snd_set_pan(ptr %p, i32 %pan) {
entry:
ret void
}

View file

@ -54,25 +54,16 @@ declare float @llvm.fabs.f32(float)
@.wgl_attrs = private unnamed_addr constant [7 x i32] [i32 8337, i32 4, i32 8338, i32 1, i32 37158, i32 1, i32 0]
@L_wgl_hwnd = internal global ptr null
@L_wgl_hdc = internal global ptr null
@L_wgl_ctx = internal global ptr null
define i32 @cgl_offscreen() {
; lgl_wgl_core(hdc, alpha bits) -> context: a pixel format on the DC, a legacy context
; (wglCreateContextAttribsARB is only reachable through one), the 4.1 core context made
; current in its place, and the entry-point table filled. Null when any step fails.
; Shared by cgl_offscreen (a hidden window) and win32_gl.ll (the game's window).
define ptr @lgl_wgl_core(ptr %hdc, i32 %alpha) {
entry:
%cur = load ptr, ptr @L_wgl_ctx
%have = icmp ne ptr %cur, null
br i1 %have, label %ok, label %win
win:
; WS_POPUP, never shown
%w = call ptr @CreateWindowExA(i32 0, ptr @.wgl_class, ptr @.wgl_title, i32 -2147483648, i32 0, i32 0, i32 16, i32 16, ptr null, ptr null, ptr null, ptr null)
%wn = icmp eq ptr %w, null
br i1 %wn, label %fail, label %pf
pf:
store ptr %w, ptr @L_wgl_hwnd
%hdc = call ptr @GetDC(ptr %w)
store ptr %hdc, ptr @L_wgl_hdc
; PIXELFORMATDESCRIPTOR: nSize 40, nVersion 1, PFD_DRAW_TO_WINDOW|SUPPORT_OPENGL|DOUBLEBUFFER,
; 32-bit colour, 24 depth, 8 stencil
; 32-bit colour, alpha at 16, 24 depth, 8 stencil
%pfd = alloca [40 x i8], align 4
call void @llvm.memset.p0.i64(ptr %pfd, i8 0, i64 40, i1 false)
store i16 40, ptr %pfd
@ -82,6 +73,9 @@ pf:
store i32 37, ptr %fl
%cb = getelementptr i8, ptr %pfd, i64 9
store i8 32, ptr %cb
%ab = getelementptr i8, ptr %pfd, i64 16
%a8 = trunc i32 %alpha to i8
store i8 %a8, ptr %ab
%db = getelementptr i8, ptr %pfd, i64 23
store i8 24, ptr %db
%sb = getelementptr i8, ptr %pfd, i64 24
@ -91,7 +85,6 @@ pf:
%setz = icmp eq i32 %set, 0
br i1 %setz, label %fail, label %legacy
legacy:
; a legacy context first: wglCreateContextAttribsARB is only reachable through one
%old = call ptr @wglCreateContext(ptr %hdc)
%oldn = icmp eq ptr %old, null
br i1 %oldn, label %fail, label %arb
@ -108,13 +101,35 @@ core:
use:
%m2 = call i32 @wglMakeCurrent(ptr %hdc, ptr %ctx)
%d1 = call i32 @wglDeleteContext(ptr %old)
store ptr %ctx, ptr @L_wgl_ctx
%n = call i32 @lgl_win_load()
br label %ok
ret ptr %ctx
drop:
%m3 = call i32 @wglMakeCurrent(ptr null, ptr null)
%d2 = call i32 @wglDeleteContext(ptr %old)
br label %fail
fail:
ret ptr null
}
define i32 @cgl_offscreen() {
entry:
%cur = load ptr, ptr @L_wgl_ctx
%have = icmp ne ptr %cur, null
br i1 %have, label %ok, label %win
win:
; WS_POPUP, never shown
%w = call ptr @CreateWindowExA(i32 0, ptr @.wgl_class, ptr @.wgl_title, i32 -2147483648, i32 0, i32 0, i32 16, i32 16, ptr null, ptr null, ptr null, ptr null)
%wn = icmp eq ptr %w, null
br i1 %wn, label %fail, label %ctx
ctx:
store ptr %w, ptr @L_wgl_hwnd
%hdc = call ptr @GetDC(ptr %w)
%c = call ptr @lgl_wgl_core(ptr %hdc, i32 0)
%cn = icmp eq ptr %c, null
br i1 %cn, label %fail, label %keep
keep:
store ptr %c, ptr @L_wgl_ctx
br label %ok
ok:
ret i32 1
fail:
@ -415,44 +430,3 @@ entry:
%t = add i64 %sus, %frac
ret i64 %t
}
; ---- the window half, until there is one ------------------------------------
; gl.ludic names these on its is_windowed() branch. A headless build compiles that
; branch to nothing, but the names still have to resolve; a windowed Windows
; build will define them in the Win32 platform layer instead of here.
define i32 @win_gl_attach() {
entry:
ret i32 0
}
define void @win_gl_resize(i32 %w, i32 %h) {
entry:
ret void
}
define void @win_gl_swap() {
entry:
ret void
}
define i32 @win_gl_scale() {
entry:
ret i32 1
}
define void @win_gl_swap_interval(i32 %n) {
entry:
ret void
}
define void @win_gl_drawable(ptr %out) {
entry:
ret void
}
define void @win_gl_update() {
entry:
ret void
}
define void @win_gl_retina(i32 %on) {
entry:
ret void
}
define void @win_toggle_fullscreen() {
entry:
ret void
}

View file

@ -0,0 +1,44 @@
; ============================================================================
; gl_win_nowin.ll — the window half of Gl.* in a headless Windows build.
;
; gl.ludic names win_gl_* on its is_windowed() branch. A headless build compiles that
; branch to nothing, but the names still have to resolve; a windowed build links
; win32_gl.ll, which defines them on the real window, in place of this file.
; ============================================================================
define i32 @win_gl_attach() {
entry:
ret i32 0
}
define void @win_gl_resize(i32 %w, i32 %h) {
entry:
ret void
}
define void @win_gl_swap() {
entry:
ret void
}
define i32 @win_gl_scale() {
entry:
ret i32 1
}
define void @win_gl_swap_interval(i32 %n) {
entry:
ret void
}
define void @win_gl_drawable(ptr %out) {
entry:
ret void
}
define void @win_gl_update() {
entry:
ret void
}
define void @win_gl_retina(i32 %on) {
entry:
ret void
}
define void @win_toggle_fullscreen() {
entry:
ret void
}

867
runtime/native/win32.ll Normal file
View file

@ -0,0 +1,867 @@
; ============================================================================
; win32.ll — the Windows window, written in LLVM IR.
;
; The counterpart of cocoa.ll, to the same contracts, spoken to user32 and
; xinput through their C ABI. ludicc links it into a windowed Windows binary;
; the GL half (win_gl_*) is win32_gl.ll, linked only when the program uses Gl.*,
; so a software-framebuffer game never names a WGL function.
;
; win_open(w, h, scale, title) win_poll() -> key win_present(fb, w, h)
; win_running() -> bool win_close()
; win_held(out) win_mouse(out) win_pad(out) win_touch(out) win_cursor_mode(m)
; splash_show(bytes, len, bg) splash_hide() app_set_icon(bytes, len)
;
; Units and conventions carried over from cocoa.ll, because the Ludic side reads
; them without knowing which platform filled them:
;
; held keys a 256-bit set of Ludic key codes: letters lowercase, digits and
; punctuation as their unshifted character, Space 32, Enter 10,
; Esc 27, Backspace 8, Tab 9, Shift 16, Ctrl 17, Alt 18, and the
; arrows at 128 129 130 131 (up down left right)
; frame key the last key pressed this frame, arrows reported as w s a d
; mouse framebuffer pixels (client pixels / scale), top-left origin;
; buttons bit0 left, bit1 right; wheel in notches, up positive
; pads SDL button order, Q16.16 axes with Y down
;
; The process is per-monitor DPI aware: a client pixel is a screen pixel, the
; backing scale is 1, and a 1280x720 window is 1280x720 pixels on any display.
;
; Keys are read from the message queue before dispatch rather than from WM_CHAR:
; TranslateMessage is never called, so no character messages are produced, and a
; key's character comes from MapVirtualKeyA. The system keys (Alt, F10) are never
; handed to DefWindowProc, whose menu loop would stall the game's frame loop -
; Alt+F4 is honoured here instead.
; ============================================================================
declare ptr @GetModuleHandleA(ptr)
declare i16 @RegisterClassExA(ptr)
declare ptr @CreateWindowExA(i32, ptr, ptr, i32, i32, i32, i32, i32, ptr, ptr, ptr, ptr)
declare i64 @DefWindowProcA(ptr, i32, i64, i64)
declare i32 @ShowWindow(ptr, i32)
declare i32 @SetForegroundWindow(ptr)
declare i32 @PeekMessageA(ptr, ptr, i32, i32, i32)
declare i64 @DispatchMessageA(ptr)
declare ptr @GetDC(ptr)
declare i32 @AdjustWindowRectEx(ptr, i32, i32, i32)
declare i32 @GetSystemMetrics(i32)
declare i32 @GetClientRect(ptr, ptr)
declare i32 @ClientToScreen(ptr, ptr)
declare i32 @ScreenToClient(ptr, ptr)
declare i32 @GetCursorPos(ptr)
declare i32 @ClipCursor(ptr)
declare ptr @SetCursor(ptr)
declare ptr @LoadCursorA(ptr, ptr)
declare ptr @GetForegroundWindow()
declare i32 @IsWindow(ptr)
declare ptr @SetCapture(ptr)
declare i32 @ReleaseCapture()
declare i32 @MapVirtualKeyA(i32, i32)
declare i16 @GetKeyState(i32)
declare i32 @RegisterRawInputDevices(ptr, i32, i32)
declare i32 @GetRawInputData(ptr, i32, ptr, ptr, i32)
declare i32 @SetProcessDpiAwarenessContext(ptr)
declare i32 @StretchDIBits(ptr, i32, i32, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32)
declare void @Sleep(i32)
declare i32 @XInputGetState(i32, ptr)
declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
@.w_class = private unnamed_addr constant [12 x i8] c"LudicWindow\00"
; shared with win32_gl.ll, which declares them external
@W_hwnd = global ptr null
@W_hdc = global ptr null
@W_fbw = global i32 320
@W_fbh = global i32 240
@W_scale = global i32 3
@W_fb = internal global ptr null
@W_key = internal global i32 0
@W_running = internal global i32 1
@W_held = internal global [8 x i32] zeroinitializer
@W_mx = internal global i32 0
@W_my = internal global i32 0
@W_mbtn = internal global i32 0
@W_wheel = internal global i32 0
; raw mouse motion since the last win_mouse, for cursor mode 2
@W_rawdx = internal global i32 0
@W_rawdy = internal global i32 0
; 0 normal, 1 hidden, 2 locked (relative), 3 confined; see win_cursor_maintain
@W_cursor_mode = internal global i32 0
@W_cursor_hidden = internal global i32 0
@W_clipped = internal global i32 0
; ---- the window procedure ---------------------------------------------------
; Only what is SENT to a window arrives here (posted input is read in win_poll).
define internal i64 @ludic_wndproc(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp) {
entry:
switch i32 %msg, label %def [
i32 16, label %close ; WM_CLOSE: end the run; the game decides what quitting does
i32 20, label %erase ; WM_ERASEBKGND: the frame covers it, and erasing flickers
i32 8, label %blur ; WM_KILLFOCUS
i32 32, label %setcur ; WM_SETCURSOR
]
close:
store i32 0, ptr @W_running
ret i64 0
erase:
ret i64 1
blur:
; a key held when focus went away never sends its key-up to this window: drop them all,
; or the hiker walks on by himself after an Alt-Tab
call void @llvm.memset.p0.i64(ptr @W_held, i8 0, i64 32, i1 false)
store i32 0, ptr @W_mbtn
br label %def
setcur:
; a hidden cursor is hidden by answering WM_SETCURSOR with no cursor over the client
; area, which needs no ShowCursor counter to keep balanced
%hid = load i32, ptr @W_cursor_hidden
%ishid = icmp ne i32 %hid, 0
%hit = and i64 %lp, 65535
%client = icmp eq i64 %hit, 1 ; HTCLIENT
%both = and i1 %ishid, %client
br i1 %both, label %nocur, label %def
nocur:
%c = call ptr @SetCursor(ptr null)
ret i64 1
def:
%r = call i64 @DefWindowProcA(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp)
ret i64 %r
}
define void @win_open(i32 %w, i32 %h, i32 %scale, ptr %title) {
entry:
%wc = alloca [80 x i8], align 8
%rc = alloca [16 x i8], align 4
%rid = alloca [16 x i8], align 8
store i32 %w, ptr @W_fbw
store i32 %h, ptr @W_fbh
store i32 %scale, ptr @W_scale
%have = load ptr, ptr @W_hwnd
%again = icmp ne ptr %have, null
br i1 %again, label %out, label %make
make:
; DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
%dpi = call i32 @SetProcessDpiAwarenessContext(ptr inttoptr (i64 -4 to ptr))
%inst = call ptr @GetModuleHandleA(ptr null)
; 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)
store i32 80, ptr %wc
%wst = getelementptr i8, ptr %wc, i64 4
store i32 35, ptr %wst ; CS_OWNDC | CS_HREDRAW | CS_VREDRAW
%wwp = getelementptr i8, ptr %wc, i64 8
store ptr @ludic_wndproc, ptr %wwp
%whi = getelementptr i8, ptr %wc, i64 24
store ptr %inst, ptr %whi
%arrow = call ptr @LoadCursorA(ptr null, ptr inttoptr (i64 32512 to ptr)) ; IDC_ARROW
%whc = getelementptr i8, ptr %wc, i64 40
store ptr %arrow, ptr %whc
%wcn = getelementptr i8, ptr %wc, i64 64
store ptr @.w_class, ptr %wcn
%atom = call i16 @RegisterClassExA(ptr %wc)
; the client area is w*scale x h*scale pixels; the frame goes round it
%pw = mul i32 %w, %scale
%ph = mul i32 %h, %scale
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
%adj = call i32 @AdjustWindowRectEx(ptr %rc, i32 13565952, i32 0, i32 0) ; WS_OVERLAPPEDWINDOW
%rl = load i32, ptr %rc
%rtp = getelementptr i8, ptr %rc, i64 4
%rt = load i32, ptr %rtp
%rr = load i32, ptr %rcr
%rb = load i32, ptr %rcb
%ww = sub i32 %rr, %rl
%wh = sub i32 %rb, %rt
; centred on the primary display
%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
; WS_OVERLAPPEDWINDOW | WS_VISIBLE
%hwnd = call ptr @CreateWindowExA(i32 0, ptr @.w_class, ptr %title, i32 282001408, i32 %x, i32 %y, i32 %ww, i32 %wh, ptr null, ptr null, ptr %inst, ptr null)
%nowin = icmp eq ptr %hwnd, null
br i1 %nowin, label %out, label %show
show:
store ptr %hwnd, ptr @W_hwnd
%hdc = call ptr @GetDC(ptr %hwnd)
store ptr %hdc, ptr @W_hdc
%s1 = call i32 @ShowWindow(ptr %hwnd, i32 5) ; SW_SHOW
%s2 = call i32 @SetForegroundWindow(ptr %hwnd)
; RAWINPUTDEVICE { usUsagePage 1, usUsage 2 (mouse), dwFlags 0, hwndTarget }: the raw
; motion cursor mode 2 turns into mouse_dx / mouse_dy
call void @llvm.memset.p0.i64(ptr %rid, i8 0, i64 16, i1 false)
store i16 1, ptr %rid
%ru = getelementptr i8, ptr %rid, i64 2
store i16 2, ptr %ru
%rh = getelementptr i8, ptr %rid, i64 8
store ptr %hwnd, ptr %rh
%reg = call i32 @RegisterRawInputDevices(ptr %rid, i32 1, i32 16)
store i32 1, ptr @W_running
br label %out
out:
ret void
}
; set (%on != 0) or clear a key's bit in the 256-bit held set
define void @win_held_bit(i32 %k, i32 %on) {
entry:
%lo = icmp slt i32 %k, 0
%hi = icmp sgt i32 %k, 255
%oob = or i1 %lo, %hi
br i1 %oob, label %ret, label %go
go:
%w = ashr i32 %k, 5
%b = and i32 %k, 31
%m = shl i32 1, %b
%p = getelementptr [8 x i32], ptr @W_held, i32 0, i32 %w
%cur = load i32, ptr %p
%onb = icmp ne i32 %on, 0
br i1 %onb, label %set, label %clr
set:
%sv = or i32 %cur, %m
store i32 %sv, ptr %p
br label %ret
clr:
%nm = xor i32 %m, -1
%cv = and i32 %cur, %nm
store i32 %cv, ptr %p
br label %ret
ret:
ret void
}
; the unshifted character a virtual key types, 0 for none (F-keys, Home, ...)
define internal i32 @w_vk_char(i32 %vk) {
entry:
%c = call i32 @MapVirtualKeyA(i32 %vk, i32 2) ; MAPVK_VK_TO_CHAR; the top bit marks a dead key
%c7 = and i32 %c, 32767
ret i32 %c7
}
; the held-set code of a virtual key (cocoa.ll's ev_keyval)
define internal i32 @w_keyval(i64 %vk64) {
entry:
%vk = trunc i64 %vk64 to i32
switch i32 %vk, label %chars [
i32 38, label %up
i32 40, label %down
i32 37, label %left
i32 39, label %right
i32 32, label %space
i32 13, label %enter
i32 27, label %esc
i32 16, label %shift
i32 17, label %ctrl
i32 18, label %alt
i32 8, label %bksp
i32 9, label %tab
]
up: ret i32 128
down: ret i32 129
left: ret i32 130
right: ret i32 131
space: ret i32 32
enter: ret i32 10
esc: ret i32 27
shift: ret i32 16
ctrl: ret i32 17
alt: ret i32 18
bksp: ret i32 8
tab: ret i32 9
chars:
%c = call i32 @w_vk_char(i32 %vk)
; the held set is keyed by the key, not the case: fold A-Z (Shift is its own code)
%isA = icmp sge i32 %c, 65
%isZ = icmp sle i32 %c, 90
%upper = and i1 %isA, %isZ
%lower = add i32 %c, 32
%f = select i1 %upper, i32 %lower, i32 %c
ret i32 %f
}
; the frame key of a virtual key (cocoa.ll's win_poll): arrows as wasd, a letter in the
; case Shift gives it, and 0 for a key that types nothing
define internal i32 @w_framekey(i64 %vk64) {
entry:
%vk = trunc i64 %vk64 to i32
switch i32 %vk, label %chars [
i32 38, label %up
i32 40, label %down
i32 37, label %left
i32 39, label %right
i32 32, label %space
i32 13, label %enter
i32 27, label %esc
i32 16, label %none
i32 17, label %none
i32 18, label %none
]
up: ret i32 119
down: ret i32 115
left: ret i32 97
right: ret i32 100
space: ret i32 32
enter: ret i32 10
esc: ret i32 27
none: ret i32 0
chars:
%c = call i32 @w_vk_char(i32 %vk)
%isA = icmp sge i32 %c, 65
%isZ = icmp sle i32 %c, 90
%upper = and i1 %isA, %isZ
%ks = call i16 @GetKeyState(i32 16)
%shifted = icmp slt i16 %ks, 0
%keepup = and i1 %upper, %shifted
%fold = xor i1 %keepup, true
%dofold = and i1 %upper, %fold
%lower = add i32 %c, 32
%f = select i1 %dofold, i32 %lower, i32 %c
ret i32 %f
}
; Drain the message queue: keys, buttons, the wheel and raw motion are read here,
; everything else is dispatched to the window procedure. Returns the frame key.
define i32 @win_poll() {
entry:
%msg = alloca [48 x i8], align 8 ; MSG: message 8, wParam 16, lParam 24
%rbuf = alloca [48 x i8], align 8 ; RAWINPUT (mouse): header 24, RAWMOUSE 24
%rsz = alloca i32, align 4
store i32 0, ptr @W_key
%hwnd = load ptr, ptr @W_hwnd
%nohwnd = icmp eq ptr %hwnd, null
br i1 %nohwnd, label %done, label %pump
pump:
%got = call i32 @PeekMessageA(ptr %msg, ptr null, i32 0, i32 0, i32 1) ; PM_REMOVE
%any = icmp ne i32 %got, 0
br i1 %any, label %handle, label %finish
handle:
%mp = getelementptr i8, ptr %msg, i64 8
%m = load i32, ptr %mp
%wpp = getelementptr i8, ptr %msg, i64 16
%wp = load i64, ptr %wpp
%lpp = getelementptr i8, ptr %msg, i64 24
%lp = load i64, ptr %lpp
switch i32 %m, label %dispatch [
i32 256, label %kdown ; WM_KEYDOWN
i32 260, label %kdown ; WM_SYSKEYDOWN
i32 257, label %kup ; WM_KEYUP
i32 261, label %kup ; WM_SYSKEYUP
i32 513, label %ldown ; WM_LBUTTONDOWN
i32 514, label %lup ; WM_LBUTTONUP
i32 516, label %rdown ; WM_RBUTTONDOWN
i32 517, label %rup ; WM_RBUTTONUP
i32 522, label %wheel ; WM_MOUSEWHEEL
i32 255, label %raw ; WM_INPUT
]
kdown:
%hv = call i32 @w_keyval(i64 %wp)
call void @win_held_bit(i32 %hv, i32 1)
%isf4 = icmp eq i64 %wp, 115 ; VK_F4
%issys = icmp eq i32 %m, 260
%altf4 = and i1 %isf4, %issys
br i1 %altf4, label %quit, label %fkey
quit:
store i32 0, ptr @W_running
br label %pump
fkey:
%fk = call i32 @w_framekey(i64 %wp)
%hasfk = icmp ne i32 %fk, 0
br i1 %hasfk, label %setfk, label %pump
setfk:
store i32 %fk, ptr @W_key
br label %pump
kup:
%uv = call i32 @w_keyval(i64 %wp)
call void @win_held_bit(i32 %uv, i32 0)
br label %pump
ldown:
%lb = load i32, ptr @W_mbtn
%lb2 = or i32 %lb, 1
store i32 %lb2, ptr @W_mbtn
%cap1 = call ptr @SetCapture(ptr %hwnd) ; so the release is seen outside the window too
br label %dispatch
lup:
%lc = load i32, ptr @W_mbtn
%lc2 = and i32 %lc, -2
store i32 %lc2, ptr @W_mbtn
br label %release
rdown:
%rbn = load i32, ptr @W_mbtn
%rbn2 = or i32 %rbn, 2
store i32 %rbn2, ptr @W_mbtn
%cap2 = call ptr @SetCapture(ptr %hwnd)
br label %dispatch
rup:
%rcn = load i32, ptr @W_mbtn
%rcn2 = and i32 %rcn, -3
store i32 %rcn2, ptr @W_mbtn
br label %release
release:
%still = load i32, ptr @W_mbtn
%nobtn = icmp eq i32 %still, 0
br i1 %nobtn, label %uncap, label %dispatch
uncap:
%rel = call i32 @ReleaseCapture()
br label %dispatch
wheel:
; the high word of wParam is the signed delta, 120 to a notch, positive away from the user
%hiw = lshr i64 %wp, 16
%d16 = trunc i64 %hiw to i16
%d32 = sext i16 %d16 to i32
%notches = sdiv i32 %d32, 120
%wv = load i32, ptr @W_wheel
%wv2 = add i32 %wv, %notches
store i32 %wv2, ptr @W_wheel
br label %pump
raw:
%hraw = inttoptr i64 %lp to ptr
store i32 48, ptr %rsz
%n = call i32 @GetRawInputData(ptr %hraw, i32 268435459, ptr %rbuf, ptr %rsz, i32 24) ; RID_INPUT
%okn = icmp sgt i32 %n, 43
br i1 %okn, label %rawtype, label %dispatch
rawtype:
%rtype = load i32, ptr %rbuf
%ismouse = icmp eq i32 %rtype, 0 ; RIM_TYPEMOUSE
%flp = getelementptr i8, ptr %rbuf, i64 24
%fl = load i16, ptr %flp
%abs = and i16 %fl, 1 ; MOUSE_MOVE_ABSOLUTE (a tablet, a remote desktop)
%isrel = icmp eq i16 %abs, 0
%use = and i1 %ismouse, %isrel
br i1 %use, label %rawadd, label %dispatch
rawadd:
%rxp = getelementptr i8, ptr %rbuf, i64 36
%rx = load i32, ptr %rxp
%ryp = getelementptr i8, ptr %rbuf, i64 40
%ry = load i32, ptr %ryp
%ax = load i32, ptr @W_rawdx
%ax2 = add i32 %ax, %rx
store i32 %ax2, ptr @W_rawdx
%ay = load i32, ptr @W_rawdy
%ay2 = add i32 %ay, %ry
store i32 %ay2, ptr @W_rawdy
br label %dispatch
dispatch:
%dm = call i64 @DispatchMessageA(ptr %msg)
br label %pump
finish:
call void @win_cursor_maintain()
%alive = call i32 @IsWindow(ptr %hwnd)
%dead = icmp eq i32 %alive, 0
br i1 %dead, label %closed, label %done
closed:
store i32 0, ptr @W_running
br label %done
done:
%k = load i32, ptr @W_key
ret i32 %k
}
; A software framebuffer: w*h words of 0x00RRGGBB, row 0 at the top - which is
; exactly a top-down 32-bit DIB, so StretchDIBits takes it as it is.
define void @win_present(ptr %fb, i32 %w, i32 %h) {
entry:
%bmi = alloca [44 x i8], align 4
store ptr %fb, ptr @W_fb
%hdc = load ptr, ptr @W_hdc
%nodc = icmp eq ptr %hdc, null
br i1 %nodc, label %out, label %draw
draw:
; BITMAPINFOHEADER: biSize 40, biWidth, biHeight negative (top-down), 1 plane, 32 bpp, BI_RGB
call void @llvm.memset.p0.i64(ptr %bmi, i8 0, i64 44, i1 false)
store i32 40, ptr %bmi
%bw = getelementptr i8, ptr %bmi, i64 4
store i32 %w, ptr %bw
%nh = sub i32 0, %h
%bh = getelementptr i8, ptr %bmi, i64 8
store i32 %nh, ptr %bh
%bp = getelementptr i8, ptr %bmi, i64 12
store i16 1, ptr %bp
%bb = getelementptr i8, ptr %bmi, i64 14
store i16 32, ptr %bb
%sc = load i32, ptr @W_scale
%dw = mul i32 %w, %sc
%dh = mul i32 %h, %sc
%r = call i32 @StretchDIBits(ptr %hdc, i32 0, i32 0, i32 %dw, i32 %dh, i32 0, i32 0, i32 %w, i32 %h, ptr %fb, ptr %bmi, i32 0, i32 13369376) ; DIB_RGB_COLORS, SRCCOPY
call void @Sleep(i32 16) ; ~60 fps, as cocoa.ll's usleep
br label %out
out:
ret void
}
define i32 @win_running() {
entry:
%r = load i32, ptr @W_running
ret i32 %r
}
define void @win_close() {
entry:
store i32 0, ptr @W_cursor_mode
call void @win_cursor_maintain()
store i32 0, ptr @W_running
ret void
}
define void @win_held(ptr %out) {
entry:
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %ni, %body ]
%done = icmp sge i32 %i, 8
br i1 %done, label %ret, label %body
body:
%sp = getelementptr [8 x i32], ptr @W_held, i32 0, i32 %i
%v = load i32, ptr %sp
%dp = getelementptr i32, ptr %out, i32 %i
store i32 %v, ptr %dp
%ni = add i32 %i, 1
br label %loop
ret:
ret void
}
; [x, y, buttons, wheel, raw dx, raw dy]; the wheel and the raw motion are per call
define void @win_mouse(ptr %out) {
entry:
%pt = alloca [8 x i8], align 4
%mode = load i32, ptr @W_cursor_mode
%isrel = icmp eq i32 %mode, 2
%ddx = load i32, ptr @W_rawdx
%ddy = load i32, ptr @W_rawdy
store i32 0, ptr @W_rawdx
store i32 0, ptr @W_rawdy
%fbw = load i32, ptr @W_fbw
%fbh = load i32, ptr @W_fbh
%fbwm = sub i32 %fbw, 1
%fbhm = sub i32 %fbh, 1
br i1 %isrel, label %rel, label %abs
rel:
; locked: the OS cursor is held still, so the position is a virtual one moved by the
; raw motion and clamped to the framebuffer; the motion itself is reported unclamped
%omx = load i32, ptr @W_mx
%omy = load i32, ptr @W_my
%nmx = add i32 %omx, %ddx
%nmy = add i32 %omy, %ddy
%p4r = getelementptr i32, ptr %out, i32 4
store i32 %ddx, ptr %p4r
%p5r = getelementptr i32, ptr %out, i32 5
store i32 %ddy, ptr %p5r
br label %clamp
abs:
%p4a = getelementptr i32, ptr %out, i32 4
store i32 0, ptr %p4a
%p5a = getelementptr i32, ptr %out, i32 5
store i32 0, ptr %p5a
%hwnd = load ptr, ptr @W_hwnd
%nowin = icmp eq ptr %hwnd, null
br i1 %nowin, label %emit, label %qpos
qpos:
%g = call i32 @GetCursorPos(ptr %pt)
%s = call i32 @ScreenToClient(ptr %hwnd, ptr %pt)
%cx = load i32, ptr %pt
%cyp = getelementptr i8, ptr %pt, i64 4
%cy = load i32, ptr %cyp
%sc = load i32, ptr @W_scale
%sc0 = icmp slt i32 %sc, 1
%scv = select i1 %sc0, i32 1, i32 %sc
%ax = sdiv i32 %cx, %scv
%ay = sdiv i32 %cy, %scv
br label %clamp
clamp:
%ux = phi i32 [ %nmx, %rel ], [ %ax, %qpos ]
%uy = phi i32 [ %nmy, %rel ], [ %ay, %qpos ]
%xlo = icmp slt i32 %ux, 0
%x1 = select i1 %xlo, i32 0, i32 %ux
%xhi = icmp sgt i32 %x1, %fbwm
%x2 = select i1 %xhi, i32 %fbwm, i32 %x1
%ylo = icmp slt i32 %uy, 0
%y1 = select i1 %ylo, i32 0, i32 %uy
%yhi = icmp sgt i32 %y1, %fbhm
%y2 = select i1 %yhi, i32 %fbhm, i32 %y1
store i32 %x2, ptr @W_mx
store i32 %y2, ptr @W_my
br label %emit
emit:
%mx = load i32, ptr @W_mx
store i32 %mx, ptr %out
%my = load i32, ptr @W_my
%p1 = getelementptr i32, ptr %out, i32 1
store i32 %my, ptr %p1
%mb = load i32, ptr @W_mbtn
%p2 = getelementptr i32, ptr %out, i32 2
store i32 %mb, ptr %p2
%wh = load i32, ptr @W_wheel
%p3 = getelementptr i32, ptr %out, i32 3
store i32 %wh, ptr %p3
store i32 0, ptr @W_wheel
ret void
}
; ---- the cursor ----------------------------------------------------------------
define internal void @w_cursor_hide(i32 %want) {
entry:
%cur = load i32, ptr @W_cursor_hidden
%same = icmp eq i32 %cur, %want
br i1 %same, label %out, label %change
change:
store i32 %want, ptr @W_cursor_hidden
%hide = icmp ne i32 %want, 0
br i1 %hide, label %none, label %arrow
none:
%c0 = call ptr @SetCursor(ptr null)
br label %out
arrow:
%a = call ptr @LoadCursorA(ptr null, ptr inttoptr (i64 32512 to ptr))
%c1 = call ptr @SetCursor(ptr %a)
br label %out
out:
ret void
}
; Re-derive the cursor from the mode and whether this window is in front, every frame:
; hidden for modes 1-3, held to the window's centre for 2, to its client area for 3,
; and all of it let go the moment another window comes forward. ClipCursor is global,
; so it is only released when this window was the one that set it.
define void @win_cursor_maintain() {
entry:
%rc = alloca [16 x i8], align 4
%hwnd = load ptr, ptr @W_hwnd
%nowin = icmp eq ptr %hwnd, null
br i1 %nowin, label %release, label %askfg
askfg:
%fg = call ptr @GetForegroundWindow()
%front = icmp eq ptr %fg, %hwnd
br i1 %front, label %keyed, label %release
release:
call void @w_cursor_hide(i32 0)
%was = load i32, ptr @W_clipped
%clipped = icmp ne i32 %was, 0
br i1 %clipped, label %unclip, label %out
unclip:
%u = call i32 @ClipCursor(ptr null)
store i32 0, ptr @W_clipped
br label %out
keyed:
%mode = load i32, ptr @W_cursor_mode
%any = icmp ne i32 %mode, 0
%anyi = zext i1 %any to i32
call void @w_cursor_hide(i32 %anyi)
%is2 = icmp eq i32 %mode, 2
%is3 = icmp eq i32 %mode, 3
%holds = or i1 %is2, %is3
br i1 %holds, label %rect, label %free
free:
%fwas = load i32, ptr @W_clipped
%fclipped = icmp ne i32 %fwas, 0
br i1 %fclipped, label %unclip, label %out
rect:
%gc = call i32 @GetClientRect(ptr %hwnd, ptr %rc)
%tl = call i32 @ClientToScreen(ptr %hwnd, ptr %rc)
%brp = getelementptr i8, ptr %rc, i64 8
%br = call i32 @ClientToScreen(ptr %hwnd, ptr %brp)
br i1 %is2, label %centre, label %clip
centre:
%l = load i32, ptr %rc
%tp = getelementptr i8, ptr %rc, i64 4
%t = load i32, ptr %tp
%r = load i32, ptr %brp
%bp = getelementptr i8, ptr %rc, i64 12
%b = load i32, ptr %bp
%lr = add i32 %l, %r
%cx = sdiv i32 %lr, 2
%tb = add i32 %t, %b
%cy = sdiv i32 %tb, 2
%cx1 = add i32 %cx, 1
%cy1 = add i32 %cy, 1
store i32 %cx, ptr %rc
store i32 %cy, ptr %tp
store i32 %cx1, ptr %brp
store i32 %cy1, ptr %bp
br label %clip
clip:
%cc = call i32 @ClipCursor(ptr %rc)
store i32 1, ptr @W_clipped
br label %out
out:
ret void
}
define void @win_cursor_mode(i32 %mode) {
entry:
store i32 %mode, ptr @W_cursor_mode
call void @win_cursor_maintain()
ret void
}
; ---- gamepads, through XInput --------------------------------------------------
; A stick value past its deadzone, rescaled so the edge of the deadzone is 0.0 and
; full deflection is 1.0, as Q16.16 (GameController does the same before macOS sees it).
define internal i32 @w_axis(i16 %v, i32 %dz) {
entry:
%s = sext i16 %v to i32
%neg = icmp slt i32 %s, 0
%ms = sub i32 0, %s
%a = select i1 %neg, i32 %ms, i32 %s
%inside = icmp sle i32 %a, %dz
br i1 %inside, label %zero, label %scale
zero:
ret i32 0
scale:
%past = sub i32 %a, %dz
%range = sub i32 32767, %dz
%p64 = sext i32 %past to i64
%m = mul i64 %p64, 65536
%r64 = sext i32 %range to i64
%q = sdiv i64 %m, %r64
%q32 = trunc i64 %q to i32
%big = icmp sgt i32 %q32, 65536
%cl = select i1 %big, i32 65536, i32 %q32
%mneg = sub i32 0, %cl
%res = select i1 %neg, i32 %mneg, i32 %cl
ret i32 %res
}
; bit %from of %bits, moved to bit %to
define internal i32 @w_bit(i32 %bits, i32 %from, i32 %to) {
entry:
%s = lshr i32 %bits, %from
%b = and i32 %s, 1
%o = shl i32 %b, %to
ret i32 %o
}
; [connected, mask, lx, ly, rx, ry] for pads 0-3. XINPUT_STATE: dwPacketNumber 0, then
; XINPUT_GAMEPAD at 4: wButtons 4, bLeftTrigger 6, bRightTrigger 7, sThumbLX 8, LY 10, RX 12, RY 14.
define void @win_pad(ptr %out) {
entry:
%st = alloca [16 x i8], align 4
call void @llvm.memset.p0.i64(ptr %out, i8 0, i64 96, i1 false)
br label %loop
loop:
%p = phi i32 [ 0, %entry ], [ %pn, %next ]
%more = icmp slt i32 %p, 4
br i1 %more, label %ask, label %done
ask:
call void @llvm.memset.p0.i64(ptr %st, i8 0, i64 16, i1 false)
%r = call i32 @XInputGetState(i32 %p, ptr %st)
%conn = icmp eq i32 %r, 0 ; ERROR_SUCCESS
br i1 %conn, label %pack, label %next
pack:
%base = mul i32 %p, 6
%o0 = getelementptr i32, ptr %out, i32 %base
store i32 1, ptr %o0
%btp = getelementptr i8, ptr %st, i64 4
%bt16 = load i16, ptr %btp
%bt = zext i16 %bt16 to i32
; XInput bits -> SDL order: A B X Y Back . Start LStick RStick LShoulder RShoulder Up Down Left Right
%mA = call i32 @w_bit(i32 %bt, i32 12, i32 0)
%mB = call i32 @w_bit(i32 %bt, i32 13, i32 1)
%mX = call i32 @w_bit(i32 %bt, i32 14, i32 2)
%mY = call i32 @w_bit(i32 %bt, i32 15, i32 3)
%mBk = call i32 @w_bit(i32 %bt, i32 5, i32 4)
%mSt = call i32 @w_bit(i32 %bt, i32 4, i32 6)
%mLs = call i32 @w_bit(i32 %bt, i32 6, i32 7)
%mRs = call i32 @w_bit(i32 %bt, i32 7, i32 8)
%mLb = call i32 @w_bit(i32 %bt, i32 8, i32 9)
%mRb = call i32 @w_bit(i32 %bt, i32 9, i32 10)
%mU = call i32 @w_bit(i32 %bt, i32 0, i32 11)
%mD = call i32 @w_bit(i32 %bt, i32 1, i32 12)
%mL = call i32 @w_bit(i32 %bt, i32 2, i32 13)
%mR = call i32 @w_bit(i32 %bt, i32 3, i32 14)
; the triggers are analogue; past XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30) they are pressed
%ltp = getelementptr i8, ptr %st, i64 6
%lt8 = load i8, ptr %ltp
%lt = zext i8 %lt8 to i32
%ltd = icmp sgt i32 %lt, 30
%ltb = zext i1 %ltd to i32
%mLt = shl i32 %ltb, 15
%rtp = getelementptr i8, ptr %st, i64 7
%rt8 = load i8, ptr %rtp
%rt = zext i8 %rt8 to i32
%rtd = icmp sgt i32 %rt, 30
%rtb = zext i1 %rtd to i32
%mRt = shl i32 %rtb, 16
%k1 = or i32 %mA, %mB
%k2 = or i32 %k1, %mX
%k3 = or i32 %k2, %mY
%k4 = or i32 %k3, %mBk
%k5 = or i32 %k4, %mSt
%k6 = or i32 %k5, %mLs
%k7 = or i32 %k6, %mRs
%k8 = or i32 %k7, %mLb
%k9 = or i32 %k8, %mRb
%k10 = or i32 %k9, %mU
%k11 = or i32 %k10, %mD
%k12 = or i32 %k11, %mL
%k13 = or i32 %k12, %mR
%k14 = or i32 %k13, %mLt
%mask = or i32 %k14, %mRt
%i1 = add i32 %base, 1
%o1 = getelementptr i32, ptr %out, i32 %i1
store i32 %mask, ptr %o1
; XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849, RIGHT 8689; XInput's Y is up, Ludic's is down
%lxp = getelementptr i8, ptr %st, i64 8
%lxv = load i16, ptr %lxp
%lx = call i32 @w_axis(i16 %lxv, i32 7849)
%i2 = add i32 %base, 2
%o2 = getelementptr i32, ptr %out, i32 %i2
store i32 %lx, ptr %o2
%lyp = getelementptr i8, ptr %st, i64 10
%lyv = load i16, ptr %lyp
%lyu = call i32 @w_axis(i16 %lyv, i32 7849)
%ly = sub i32 0, %lyu
%i3 = add i32 %base, 3
%o3 = getelementptr i32, ptr %out, i32 %i3
store i32 %ly, ptr %o3
%rxp = getelementptr i8, ptr %st, i64 12
%rxv = load i16, ptr %rxp
%rx = call i32 @w_axis(i16 %rxv, i32 8689)
%i4 = add i32 %base, 4
%o4 = getelementptr i32, ptr %out, i32 %i4
store i32 %rx, ptr %o4
%ryp = getelementptr i8, ptr %st, i64 14
%ryv = load i16, ptr %ryp
%ryu = call i32 @w_axis(i16 %ryv, i32 8689)
%ry = sub i32 0, %ryu
%i5 = add i32 %base, 5
%o5 = getelementptr i32, ptr %out, i32 %i5
store i32 %ry, ptr %o5
br label %next
next:
%pn = add i32 %p, 1
br label %loop
done:
ret void
}
; Touch: a Windows PC reports its touchpad as a mouse, so there are no points.
define void @win_touch(ptr %out) {
entry:
call void @llvm.memset.p0.i64(ptr %out, i8 0, i64 96, i1 false)
ret void
}
; ---- the splash and the icon ---------------------------------------------------
; Not yet: both need an image decoder (WIC), and on Windows the icon a player sees on
; the taskbar and in Explorer is the one linked into the .exe, which `ludic bundle`
; will do. Until then these keep the contract - safe to call, change nothing.
define void @splash_show(ptr %bytes, i32 %len, i32 %bg) {
entry:
ret void
}
define void @splash_hide() {
entry:
ret void
}
define void @app_set_icon(ptr %bytes, i32 %len) {
entry:
ret void
}

253
runtime/native/win32_gl.ll Normal file
View file

@ -0,0 +1,253 @@
; ============================================================================
; 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) client area of w x h pixels, centred
; 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_drawable(out) the client area in pixels
; win_toggle_fullscreen() borderless, over the monitor the window is on
; ============================================================================
@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 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 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_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:
store i32 %w, ptr @W_fbw
store i32 %h, ptr @W_fbh
store i32 1, ptr @W_scale
%fs = load i32, ptr @W_fs
%isfs = icmp ne i32 %fs, 0
br i1 %isfs, label %out, label %size
size:
call void @llvm.memset.p0.i64(ptr %rc, i8 0, i64 16, i1 false)
%rcr = getelementptr i8, ptr %rc, i64 8
store i32 %w, ptr %rcr
%rcb = getelementptr i8, ptr %rc, i64 12
store i32 %h, 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:
ret i32 1
}
; 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
}
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 for the mouse mapping
%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:
store i32 %w, ptr @W_fbw
store i32 %h, ptr @W_fbh
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: on Windows 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.
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
}