carry: the runtime and render3d work Maroon Lake builds against

Input.text, App.monitor_count / window_to_monitor / window_fixed,
gl_sleep_us, and the grass and collide changes, uncommitted on main and
depended on by the game; carried here so the language work starts from
what the game actually uses. main's working tree is untouched.
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-23 15:55:02 +03:00
parent 86492064aa
commit e6f565a6c4
17 changed files with 622 additions and 34 deletions

View file

@ -31,8 +31,17 @@
; 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. The held
; The window is a UNICODE window - RegisterClassExW, CreateWindowExW, PeekMessageW,
; DispatchMessageW, DefWindowProcW - and that is not cosmetic: only a Unicode window's WM_CHAR
; carries a UTF-16 code unit. Registered the ANSI way, TranslateMessage hands back one byte in the
; machine's code page, and a Turkish s-cedilla has nowhere to go.
;
; Keys are read from the message queue before dispatch rather than from WM_CHAR, so a BINDING is
; always the physical key. TranslateMessage IS called for key messages now, which adds WM_CHAR
; alongside - that is the only way to know what the player actually TYPED. Bindings read the
; scancode before dispatch and are unaffected; typing reads the characters. A player on a Turkish
; layout could not type c-cedilla, g-breve, dotless i, o, s-cedilla or u-umlaut at all before this,
; because the game built text from US-layout key CODES - their own name was unreachable. The held
; set is keyed by the scancode in lParam, not by the virtual key: a virtual key is
; the layout's idea of the key, so on AZERTY the key where W is printed on a US
; board arrives as VK_Z, and an input method turns every letter into VK_PROCESSKEY
@ -45,15 +54,27 @@
declare ptr @GetModuleHandleA(ptr)
declare i16 @RegisterClassExA(ptr)
declare i16 @RegisterClassExW(ptr)
declare ptr @CreateWindowExA(i32, ptr, ptr, i32, i32, i32, i32, i32, ptr, ptr, ptr, ptr)
declare ptr @CreateWindowExW(i32, ptr, ptr, i32, i32, i32, i32, i32, ptr, ptr, ptr, ptr)
declare i64 @DefWindowProcA(ptr, i32, i64, i64)
declare i64 @DefWindowProcW(ptr, i32, i64, i64)
declare i32 @ShowWindow(ptr, i32)
declare i64 @GetWindowLongPtrW(ptr, i32)
declare i64 @SetWindowLongPtrW(ptr, i32, i64)
declare i32 @SetWindowPos(ptr, ptr, i32, i32, i32, i32, i32)
declare i32 @GetWindowRect(ptr, ptr)
declare i32 @EnumDisplayDevicesW(ptr, i32, ptr, i32)
declare i32 @EnumDisplaySettingsW(ptr, i32, ptr)
declare i32 @SetForegroundWindow(ptr)
declare i32 @BringWindowToTop(ptr)
declare i32 @SetWindowTextW(ptr, ptr)
declare i32 @MultiByteToWideChar(i32, i32, ptr, i32, ptr, i32)
declare i32 @PeekMessageA(ptr, ptr, i32, i32, i32)
declare i32 @PeekMessageW(ptr, ptr, i32, i32, i32)
declare i64 @DispatchMessageA(ptr)
declare i64 @DispatchMessageW(ptr)
declare i32 @TranslateMessage(ptr)
declare ptr @GetDC(ptr)
declare i32 @AdjustWindowRectEx(ptr, i32, i32, i32)
declare i32 @GetSystemMetrics(i32)
@ -81,6 +102,11 @@ 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"
; the same name as UTF-16. A window is a UNICODE window only if its class was registered with
; RegisterClassExW, and only a Unicode window gets WM_CHAR carrying a UTF-16 code unit - on an
; ANSI window TranslateMessage hands back a byte in the machine's code page, which has nowhere to
; put a Turkish s-cedilla. That single fact is why a player could not type their own name.
@.w_classw = private unnamed_addr constant [24 x i8] c"L\00u\00d\00i\00c\00W\00i\00n\00d\00o\00w\00\00\00"
; shared with win32_gl.ll, which declares them external
@W_hwnd = global ptr null
@ -91,6 +117,11 @@ declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
@W_fb = internal global ptr null
@W_key = internal global i32 0
; what the player has typed since the game last took it: UTF-16 code units, oldest first. 64 is far
; more than a frame can produce, and an overflow drops the newest rather than wrapping onto the
; oldest - a dropped keystroke is better than a scrambled name.
@W_text = internal global [64 x i32] zeroinitializer
@W_textn = internal global i32 0
@W_running = internal global i32 1
@W_held = internal global [8 x i32] zeroinitializer
@W_mx = internal global i32 0
@ -139,7 +170,7 @@ nocur:
%c = call ptr @SetCursor(ptr null)
ret i64 1
def:
%r = call i64 @DefWindowProcA(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp)
%r = call i64 @DefWindowProcW(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp)
ret i64 %r
}
@ -183,6 +214,7 @@ done:
define void @win_open(i32 %w, i32 %h, i32 %scale, ptr %title) {
entry:
%wc = alloca [80 x i8], align 8
%tw = alloca [256 x i16], align 2 ; the window title as UTF-16, for CreateWindowExW
%rc = alloca [16 x i8], align 4
%rid = alloca [16 x i8], align 8
store i32 %w, ptr @W_fbw
@ -219,8 +251,8 @@ make:
%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)
store ptr @.w_classw, ptr %wcn
%atom = call i16 @RegisterClassExW(ptr %wc)
; the client area is w x h at both scales; the frame goes round it
%pw = mul i32 %gw, %ds
%ph = mul i32 %gh, %ds
@ -249,7 +281,16 @@ make:
%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)
; the title arrives as UTF-8 and CreateWindowExW wants UTF-16. The buffer is zeroed first, so a
; title that will not convert leaves an empty one rather than whatever was on the stack.
call void @llvm.memset.p0.i64(ptr %tw, i8 0, i64 512, i1 false)
%tnull = icmp eq ptr %title, null
br i1 %tnull, label %mkwin, label %conv
conv:
%cvn = call i32 @MultiByteToWideChar(i32 65001, i32 0, ptr %title, i32 -1, ptr %tw, i32 255)
br label %mkwin
mkwin:
%hwnd = call ptr @CreateWindowExW(i32 0, ptr @.w_classw, ptr %tw, 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:
@ -305,6 +346,164 @@ out:
; The game's window leaves the screen and comes back with its GL context or Vulkan surface
; untouched: a launcher steps aside while the game it started runs. A hidden window is
; still a window, so the run goes on. Both are no-ops before there is a window.
; App.window_fixed(on): take the resize grip and the maximise button off this window, or put them
; back. The LAUNCHER is not the game - it is a small fixed panel - but it carried the game's name in
; huge letters and a picture of the Bells, so players took it for the game and tried to make it full
; screen, then wondered why the video settings they had just changed had done nothing. A window you
; cannot maximise says what it is before any label does.
; ---- which display the window is on ---------------------------------------------------------
; Enumerated by INDEX, not through EnumDisplayMonitors, which wants a callback: EnumDisplayDevicesW
; walks the adapters by number and EnumDisplaySettingsW gives each one's position and size, so no
; function pointer has to cross the IR boundary. Only displays attached to the desktop count, so the
; numbering matches what the player sees in Windows' own display settings.
;
; DISPLAY_DEVICEW: cb 0, DeviceName 4 (32 wide chars), StateFlags 324.
; DEVMODEW: dmSize 68, dmFields 72, dmPosition 76 (x) 80 (y), dmPelsWidth 156, dmPelsHeight 160.
; DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 1; ENUM_CURRENT_SETTINGS = -1.
define i32 @win_monitor_rect(i32 %want, ptr %out) {
entry:
%dd = alloca [840 x i8], align 8
%dm = alloca [220 x i8], align 8
br label %loop
loop:
%j = phi i32 [ 0, %entry ], [ %j1, %cont ]
%found = phi i32 [ 0, %entry ], [ %found2, %cont ]
%toomany = icmp sge i32 %j, 64
br i1 %toomany, label %none, label %ask
ask:
call void @llvm.memset.p0.i64(ptr %dd, i8 0, i64 840, i1 false)
store i32 840, ptr %dd
%ok = call i32 @EnumDisplayDevicesW(ptr null, i32 %j, ptr %dd, i32 0)
%gone = icmp eq i32 %ok, 0
br i1 %gone, label %none, label %flags
flags:
%sfp = getelementptr i8, ptr %dd, i64 324
%sf = load i32, ptr %sfp
%att = and i32 %sf, 1
%live = icmp ne i32 %att, 0
br i1 %live, label %count, label %skip
count:
%isit = icmp eq i32 %found, %want
br i1 %isit, label %settings, label %inc
settings:
call void @llvm.memset.p0.i64(ptr %dm, i8 0, i64 220, i1 false)
%szp = getelementptr i8, ptr %dm, i64 68
store i16 220, ptr %szp
%namep = getelementptr i8, ptr %dd, i64 4
%got = call i32 @EnumDisplaySettingsW(ptr %namep, i32 -1, ptr %dm)
%bad = icmp eq i32 %got, 0
br i1 %bad, label %none, label %fill
fill:
%pxp = getelementptr i8, ptr %dm, i64 76
%pyp = getelementptr i8, ptr %dm, i64 80
%pwp = getelementptr i8, ptr %dm, i64 156
%php = getelementptr i8, ptr %dm, i64 160
%px = load i32, ptr %pxp
%py = load i32, ptr %pyp
%pw = load i32, ptr %pwp
%ph = load i32, ptr %php
store i32 %px, ptr %out
%o1 = getelementptr i32, ptr %out, i32 1
store i32 %py, ptr %o1
%o2 = getelementptr i32, ptr %out, i32 2
store i32 %pw, ptr %o2
%o3 = getelementptr i32, ptr %out, i32 3
store i32 %ph, ptr %o3
ret i32 1
inc: ; a display, but not the one asked for
%foundinc = add i32 %found, 1
br label %cont
skip: ; an adapter with no desktop on it: not a display at all
br label %cont
cont:
%found2 = phi i32 [ %foundinc, %inc ], [ %found, %skip ]
%j1 = add i32 %j, 1
br label %loop
none:
ret i32 0
}
; how many displays the desktop is spread over
define i32 @win_monitor_count() {
entry:
%r = alloca [16 x i8], align 4
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %i1, %more ]
%ok = call i32 @win_monitor_rect(i32 %i, ptr %r)
%has = icmp ne i32 %ok, 0
br i1 %has, label %more, label %done
more:
%i1 = add i32 %i, 1
br label %loop
done:
ret i32 %i
}
; Put the window on display `i`, centred. A player with two screens had no way to say which one the
; game should use: the window opened where it opened, and going full screen only ever covered the
; display it was already on.
define void @app_window_to_monitor(i32 %i) {
entry:
%h = load ptr, ptr @W_hwnd
%none = icmp eq ptr %h, null
br i1 %none, label %out, label %go
go:
%mr = alloca [16 x i8], align 4
%wr = alloca [16 x i8], align 4
%ok = call i32 @win_monitor_rect(i32 %i, ptr %mr)
%bad = icmp eq i32 %ok, 0
br i1 %bad, label %out, label %move
move:
%gw = call i32 @GetWindowRect(ptr %h, ptr %wr)
%l = load i32, ptr %wr
%t1p = getelementptr i32, ptr %wr, i32 1
%t = load i32, ptr %t1p
%r2p = getelementptr i32, ptr %wr, i32 2
%rr = load i32, ptr %r2p
%b3p = getelementptr i32, ptr %wr, i32 3
%bb = load i32, ptr %b3p
%ww = sub i32 %rr, %l
%wh = sub i32 %bb, %t
%mx = load i32, ptr %mr
%my1p = getelementptr i32, ptr %mr, i32 1
%my = load i32, ptr %my1p
%mw2p = getelementptr i32, ptr %mr, i32 2
%mw = load i32, ptr %mw2p
%mh3p = getelementptr i32, ptr %mr, i32 3
%mh = load i32, ptr %mh3p
%dx = sub i32 %mw, %ww
%dy = sub i32 %mh, %wh
%hx = sdiv i32 %dx, 2
%hy = sdiv i32 %dy, 2
%nx = add i32 %mx, %hx
%ny = add i32 %my, %hy
; SWP_NOSIZE 1 | SWP_NOZORDER 4
%sp = call i32 @SetWindowPos(ptr %h, ptr null, i32 %nx, i32 %ny, i32 0, i32 0, i32 5)
br label %out
out:
ret void
}
define void @app_window_fixed(i32 %on) {
entry:
%h = load ptr, ptr @W_hwnd
%none = icmp eq ptr %h, null
br i1 %none, label %out, label %go
go:
%st = call i64 @GetWindowLongPtrW(ptr %h, i32 -16) ; GWL_STYLE
; WS_THICKFRAME 0x00040000 | WS_MAXIMIZEBOX 0x00010000
%fixed = icmp ne i32 %on, 0
%cleared = and i64 %st, -327681 ; ~0x00050000
%restored = or i64 %st, 327680 ; | 0x00050000
%new = select i1 %fixed, i64 %cleared, i64 %restored
%old = call i64 @SetWindowLongPtrW(ptr %h, i32 -16, i64 %new)
; SWP_NOMOVE 2 | SWP_NOSIZE 1 | SWP_NOZORDER 4 | SWP_FRAMECHANGED 32
%r = call i32 @SetWindowPos(ptr %h, ptr null, i32 0, i32 0, i32 0, i32 0, i32 39)
br label %out
out:
ret void
}
define void @app_window_hide() {
entry:
%h = load ptr, ptr @W_hwnd
@ -604,7 +803,7 @@ entry:
%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
%got = call i32 @PeekMessageW(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:
@ -625,8 +824,29 @@ handle:
i32 517, label %rup ; WM_RBUTTONUP
i32 522, label %wheel ; WM_MOUSEWHEEL
i32 255, label %raw ; WM_INPUT
i32 258, label %wchar ; WM_CHAR
]
wchar:
; wParam is one UTF-16 code unit. Control characters are not text: Backspace, Enter, Tab and Esc
; are actions the menu kit reads as keys, and letting them through would put a glyph in the name.
%ch = trunc i64 %wp to i32
%isctl = icmp ult i32 %ch, 32
%isdel = icmp eq i32 %ch, 127
%skip = or i1 %isctl, %isdel
br i1 %skip, label %pump, label %wput
wput:
%tn = load i32, ptr @W_textn
%room = icmp slt i32 %tn, 64
br i1 %room, label %wstore, label %pump
wstore:
%tp = getelementptr [64 x i32], ptr @W_text, i64 0, i32 %tn
store i32 %ch, ptr %tp
%tn1 = add i32 %tn, 1
store i32 %tn1, ptr @W_textn
br label %pump
kdown:
; produces the WM_CHAR above for whatever this key types on the player's own layout
%tm = call i32 @TranslateMessage(ptr %msg)
%hv = call i32 @w_keyval(i64 %wp, i64 %lp)
call void @win_held_bit(i32 %hv, i32 1)
%isf4 = icmp eq i64 %wp, 115 ; VK_F4
@ -714,7 +934,7 @@ rawadd:
store i32 %ay2, ptr @W_rawdy
br label %dispatch
dispatch:
%dm = call i64 @DispatchMessageA(ptr %msg)
%dm = call i64 @DispatchMessageW(ptr %msg)
br label %pump
finish:
call void @win_cursor_maintain()
@ -775,6 +995,31 @@ entry:
ret void
}
; Take what has been typed since the last call: UTF-16 code units into `out` (one per word),
; returning how many. Draining here rather than in the pump keeps a frame that never asks from
; growing the ring for ever.
define i32 @win_text(ptr %out, i32 %max) {
entry:
%n = load i32, ptr @W_textn
%fits = icmp sle i32 %n, %max
%cnt = select i1 %fits, i32 %n, i32 %max
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %i1, %body ]
%go = icmp slt i32 %i, %cnt
br i1 %go, label %body, label %done
body:
%sp = getelementptr [64 x i32], ptr @W_text, i64 0, i32 %i
%v = load i32, ptr %sp
%dp = getelementptr i32, ptr %out, i32 %i
store i32 %v, ptr %dp
%i1 = add i32 %i, 1
br label %loop
done:
store i32 0, ptr @W_textn
ret i32 %cnt
}
define void @win_held(ptr %out) {
entry:
br label %loop
@ -1337,7 +1582,7 @@ endp:
%e = call i32 @EndPaint(ptr %hwnd, ptr %ps)
ret i64 0
def:
%d = call i64 @DefWindowProcA(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp)
%d = call i64 @DefWindowProcW(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp)
ret i64 %d
}