feat(windows): Gl.* on Windows, through WGL and a driver-filled thunk table
gl_win.ll creates a hidden-window WGL 4.1 core context and carries gl.ll's float/memory helpers, with ldexp and QueryPerformanceCounter in place of the libSystem calls. glgen now also writes gl_thunks_win.ll: the same 478 thunks, calling through pointers that @lgl_win_load fills from wglGetProcAddress (and opengl32.dll for GL 1.1). ludicc links the pair against opengl32/gdi32/user32 on a Windows target. Verified: gl_api.ludic and gl_thunks.ll regenerate byte-identically, ludic-dev test 135/135, selfhost-test 32/32, and headless gl_triangle on an RTX 3070 Ti matches the macOS frame to within one level per channel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
5801005fca
commit
cb05721a89
6 changed files with 13177 additions and 301 deletions
|
|
@ -530,13 +530,138 @@ function cmd_glgen() -> int {
|
|||
|
||||
var api_out = "runtime/native/gl_api.ludic"
|
||||
var thunk_out = "runtime/native/gl_thunks.ll"
|
||||
if check { api_out = tmp_path("gl_api.ludic"); thunk_out = tmp_path("gl_thunks.ll") }
|
||||
var win_out = "runtime/native/gl_thunks_win.ll"
|
||||
if check { api_out = tmp_path("gl_api.ludic"); thunk_out = tmp_path("gl_thunks.ll"); win_out = tmp_path("gl_thunks_win.ll") }
|
||||
if not glg_emit_ludic(api_out) { err("glgen: cannot write gl_api.ludic\n"); return 1 }
|
||||
if not glg_emit_thunks(thunk_out) { err("glgen: cannot write gl_thunks.ll\n"); return 1 }
|
||||
if not glg_emit_thunks_win(win_out) { err("glgen: cannot write gl_thunks_win.ll\n"); return 1 }
|
||||
if check {
|
||||
if not shq(`cmp -s {api_out} runtime/native/gl_api.ludic`) { err("gl_api.ludic drifted from gl3.h (run: ludic-dev glgen)\n"); return 1 }
|
||||
if not shq(`cmp -s {thunk_out} runtime/native/gl_thunks.ll`) { err("gl_thunks.ll drifted from gl3.h (run: ludic-dev glgen)\n"); return 1 }
|
||||
if not shq(`cmp -s {win_out} runtime/native/gl_thunks_win.ll`) { err("gl_thunks_win.ll drifted from gl3.h (run: ludic-dev glgen)\n"); return 1 }
|
||||
}
|
||||
print(`OK {string(len(glg_cname))} constants, {string(len(glg_fname))} entry points`)
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- emit gl_thunks_win.ll --------------------------------------------------
|
||||
# The Windows thunks. Same Ludic ABI and the same conversions as gl_thunks.ll, but
|
||||
# no thunk names a gl* symbol: opengl32.dll exports only OpenGL 1.1, and every later
|
||||
# entry point belongs to the driver, reachable only through wglGetProcAddress while
|
||||
# a context is current. So each thunk calls through @lgl_p_<name>, and
|
||||
# @lgl_win_load (called by gl_win.ll once its 4.1 core context is current) fills
|
||||
# the table - from the driver first, then from opengl32.dll for the 1.1 functions,
|
||||
# which wglGetProcAddress does not return.
|
||||
function glg_emit_thunks_win(path: pointer) -> bool {
|
||||
let b = sb_new()
|
||||
sb_puts(b, "; ============================================================================\n")
|
||||
sb_puts(b, "; gl_thunks_win.ll — one thunk per OpenGL 4.1 core entry point, for Windows.\n")
|
||||
sb_puts(b, "; GENERATED by `ludic-dev glgen` from gl3.h. The thunks of gl_thunks.ll, calling\n")
|
||||
sb_puts(b, "; through @lgl_p_* pointers that @lgl_win_load fills from the current context.\n")
|
||||
sb_puts(b, "; ============================================================================\n")
|
||||
sb_puts(b, "\n")
|
||||
sb_puts(b, "declare ptr @wglGetProcAddress(ptr)\n")
|
||||
sb_puts(b, "declare ptr @LoadLibraryA(ptr)\n")
|
||||
sb_puts(b, "declare ptr @GetProcAddress(ptr, ptr)\n")
|
||||
sb_puts(b, "@.lgl_dll = private unnamed_addr constant [13 x i8] c\"opengl32.dll\\00\"\n")
|
||||
var i = 0
|
||||
while i < len(glg_fname) {
|
||||
let nm = glg_fname[i]
|
||||
sb_puts(b, `@lgl_p_{nm} = internal global ptr null\n`)
|
||||
sb_puts(b, `@.lgl_n_{nm} = private unnamed_addr constant [{string(len(nm) + 3)} x i8] c"gl{nm}\\00"\n`)
|
||||
i += 1
|
||||
}
|
||||
sb_puts(b, "\n")
|
||||
i = 0
|
||||
while i < len(glg_fname) {
|
||||
let cret = glg_ir_ty(glg_frbase[i], glg_frstar[i])
|
||||
let lret = glg_abi_ty(cret)
|
||||
let sig = sb_new()
|
||||
let body = sb_new()
|
||||
let call = sb_new()
|
||||
var p = 0
|
||||
while p < glg_fpcnt[i] {
|
||||
let ix = glg_fpoff[i] + p
|
||||
let ct = glg_ir_ty(glg_ppbase[ix], glg_ppstar[ix])
|
||||
let lt = glg_abi_ty(ct)
|
||||
if p > 0 { sb_puts(sig, ", "); sb_puts(call, ", ") }
|
||||
sb_puts(sig, `{lt} %a{string(p)}`)
|
||||
if ct == lt {
|
||||
sb_puts(call, `{ct} %a{string(p)}`)
|
||||
} else {
|
||||
if ct == "i8" or ct == "i16" {
|
||||
sb_puts(body, ` %c{string(p)} = trunc i32 %a{string(p)} to {ct}\n`)
|
||||
sb_puts(call, `{ct} %c{string(p)}`)
|
||||
} else {
|
||||
sb_puts(body, ` %f{string(p)} = sitofp i32 %a{string(p)} to {ct}\n`)
|
||||
sb_puts(body, ` %c{string(p)} = fmul {ct} %f{string(p)}, 0x3EF0000000000000\n`)
|
||||
sb_puts(call, `{ct} %c{string(p)}`)
|
||||
}
|
||||
}
|
||||
p += 1
|
||||
}
|
||||
sb_puts(b, `define {lret} @lgl_{glg_fname[i]}({sb_str(sig)}) `)
|
||||
sb_puts(b, "{\n")
|
||||
sb_puts(b, "entry:\n")
|
||||
sb_puts(b, ` %fp = load ptr, ptr @lgl_p_{glg_fname[i]}\n`)
|
||||
sb_puts(b, sb_str(body))
|
||||
let invoke = `call {cret} %fp({sb_str(call)})`
|
||||
if cret == "void" {
|
||||
sb_puts(b, ` {invoke}\n`)
|
||||
sb_puts(b, " ret void\n")
|
||||
} else {
|
||||
if cret == lret {
|
||||
sb_puts(b, ` %r = {invoke}\n`)
|
||||
sb_puts(b, ` ret {lret} %r\n`)
|
||||
} else {
|
||||
if cret == "i8" or cret == "i16" {
|
||||
sb_puts(b, ` %r = {invoke}\n`)
|
||||
sb_puts(b, ` %z = zext {cret} %r to i32\n`)
|
||||
sb_puts(b, " ret i32 %z\n")
|
||||
} else {
|
||||
sb_puts(b, ` %r = {invoke}\n`)
|
||||
sb_puts(b, ` %m = fmul {cret} %r, 65536.0\n`)
|
||||
sb_puts(b, ` %z = fptosi {cret} %m to i32\n`)
|
||||
sb_puts(b, " ret i32 %z\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
sb_puts(b, "}\n")
|
||||
i += 1
|
||||
}
|
||||
|
||||
# the loader: wglGetProcAddress returns 0, 1, 2, 3 or -1 for a name it does not
|
||||
# know (drivers differ), and never answers for a 1.1 function; both fall back to
|
||||
# opengl32.dll's own export. Returns how many names are still unresolved.
|
||||
sb_puts(b, "\ndefine i32 @lgl_win_load() {\n")
|
||||
sb_puts(b, "entry:\n %dll = call ptr @LoadLibraryA(ptr @.lgl_dll)\n br label %f0\n")
|
||||
i = 0
|
||||
while i < len(glg_fname) {
|
||||
let nm = glg_fname[i]
|
||||
let k = string(i)
|
||||
sb_puts(b, `f{k}:\n %m{k} = phi i32 [ {glg_load_in(i)} ]\n`)
|
||||
sb_puts(b, ` %w{k} = call ptr @wglGetProcAddress(ptr @.lgl_n_{nm})\n`)
|
||||
sb_puts(b, ` %wi{k} = ptrtoint ptr %w{k} to i64\n %wa{k} = add i64 %wi{k}, 1\n %bad{k} = icmp ult i64 %wa{k}, 5\n`)
|
||||
sb_puts(b, ` br i1 %bad{k}, label %g{k}, label %s{k}\n`)
|
||||
sb_puts(b, `g{k}:\n %e{k} = call ptr @GetProcAddress(ptr %dll, ptr @.lgl_n_{nm})\n br label %s{k}\n`)
|
||||
sb_puts(b, `s{k}:\n %p{k} = phi ptr [ %w{k}, %f{k} ], [ %e{k}, %g{k} ]\n store ptr %p{k}, ptr @lgl_p_{nm}\n`)
|
||||
sb_puts(b, ` %u{k} = icmp eq ptr %p{k}, null\n %ui{k} = zext i1 %u{k} to i32\n %m{string(i + 1)}x = add i32 %m{k}, %ui{k}\n`)
|
||||
sb_puts(b, ` br label %f{string(i + 1)}\n`)
|
||||
i += 1
|
||||
}
|
||||
let n = string(len(glg_fname))
|
||||
sb_puts(b, `f{n}:\n %m{n} = phi i32 [ %m{n}x, %s{glg_prev_idx(len(glg_fname))} ]\n ret i32 %m{n}\n`)
|
||||
sb_puts(b, "}\n")
|
||||
return write_file(path, sb_str(b))
|
||||
}
|
||||
|
||||
# the loader's blocks: f0 is entered only from `entry` with a count of 0, every
|
||||
# later fN only from s(N-1), carrying the count that block computed
|
||||
function glg_load_in(i: int) -> pointer {
|
||||
if i == 0 { return "0, %entry" }
|
||||
return `%m{string(i)}x, %s{string(i - 1)}`
|
||||
}
|
||||
function glg_prev_idx(i: int) -> pointer {
|
||||
if i == 0 { return "0" }
|
||||
return string(i - 1)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue