feat(render3d): Vulkan in a macOS window through MoltenVK; window built-ins take a slice's elements; the NEAR_FADE SPIR-V
- a CAMetalLayer on the view (cocoa.ll win_metal_layer), VK_EXT_metal_surface, QuartzCore linked with Vk.*; the drawable measured after the layer sets the backing scale - vk_mac.ll opens MoltenVK directly after any loader: a bundle ships only libMoltenVK.dylib - a covered window is not presented to (win_visible); one frame in flight on macOS (R3D_VK_INFLIGHT), with images, buffers and descriptor pools held until it is done - win_held / win_mouse / win_pad / win_touch / win_text / win_present pass slice elements (arg_buf): every windowed program died on its first input poll - variants.list and SPIR-V for the three NEAR_FADE foliage programs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9862ec82e3
commit
38d4ea72b1
25 changed files with 627 additions and 164 deletions
|
|
@ -1607,6 +1607,14 @@ go:
|
|||
%scale = select i1 %ison, i32 %bsv, i32 1
|
||||
store i32 %scale, ptr @W_glscale
|
||||
call void @win_gl_update()
|
||||
; a Metal layer (Vulkan) draws at the same scale
|
||||
%ml = load ptr, ptr @W_mtl
|
||||
%noml = icmp eq ptr %ml, null
|
||||
br i1 %noml, label %out, label %mtl
|
||||
mtl:
|
||||
%scd = sitofp i32 %scale to double
|
||||
%sel_csc = call ptr @sel_registerName(ptr @.s_setcsc)
|
||||
%rm = call ptr (ptr, ptr, double) @objc_msgSend(ptr %ml, ptr %sel_csc, double %scd)
|
||||
br label %out
|
||||
out:
|
||||
ret void
|
||||
|
|
@ -2035,3 +2043,81 @@ define ptr @win_native_instance() {
|
|||
entry:
|
||||
ret ptr null
|
||||
}
|
||||
|
||||
; ---- a Metal layer on the view, for Vulkan through MoltenVK -----------------------------
|
||||
; VK_EXT_metal_surface presents to a CAMetalLayer, so the view is made layer-hosting with one
|
||||
; (the layer set before wantsLayer, which is what makes the view host it rather than back it).
|
||||
; Its contentsScale is the window's backing scale, or the swapchain's pixels are stretched over
|
||||
; a Retina window's points; @W_glscale follows it, as win_gl_attach sets it for OpenGL. The class
|
||||
; is looked up by name: the program links QuartzCore when it uses Vk.* (null without it).
|
||||
; win_metal_layer() -> CAMetalLayer* | null (no window, or no QuartzCore)
|
||||
@.c_mtllayer = private unnamed_addr constant [13 x i8] c"CAMetalLayer\00"
|
||||
@.s_layer = private unnamed_addr constant [6 x i8] c"layer\00"
|
||||
@.s_retain = private unnamed_addr constant [7 x i8] c"retain\00"
|
||||
@.s_setlayer = private unnamed_addr constant [10 x i8] c"setLayer:\00"
|
||||
@.s_wantsl = private unnamed_addr constant [15 x i8] c"setWantsLayer:\00"
|
||||
@.s_setcsc = private unnamed_addr constant [18 x i8] c"setContentsScale:\00"
|
||||
@W_mtl = internal global ptr null
|
||||
|
||||
define ptr @win_metal_layer() {
|
||||
entry:
|
||||
%have = load ptr, ptr @W_mtl
|
||||
%got = icmp ne ptr %have, null
|
||||
br i1 %got, label %done, label %make
|
||||
make:
|
||||
%view = load ptr, ptr @W_view
|
||||
%win = load ptr, ptr @W_win
|
||||
%noview = icmp eq ptr %view, null
|
||||
%nowin = icmp eq ptr %win, null
|
||||
%nothing = or i1 %noview, %nowin
|
||||
br i1 %nothing, label %none, label %cls
|
||||
cls:
|
||||
%c = call ptr @objc_getClass(ptr @.c_mtllayer)
|
||||
%noc = icmp eq ptr %c, null
|
||||
br i1 %noc, label %none, label %go
|
||||
go:
|
||||
%sel_l = call ptr @sel_registerName(ptr @.s_layer)
|
||||
%l0 = call ptr @objc_msgSend(ptr %c, ptr %sel_l)
|
||||
%sel_r = call ptr @sel_registerName(ptr @.s_retain)
|
||||
%l = call ptr @objc_msgSend(ptr %l0, ptr %sel_r)
|
||||
%sel_bs = call ptr @sel_registerName(ptr @.s_bscale)
|
||||
%bs = call double (ptr, ptr) @objc_msgSend(ptr %win, ptr %sel_bs)
|
||||
%bsok = fcmp ogt double %bs, 0.0
|
||||
%scale = select i1 %bsok, double %bs, double 1.0
|
||||
%sel_cs = call ptr @sel_registerName(ptr @.s_setcsc)
|
||||
%r0 = call ptr (ptr, ptr, double) @objc_msgSend(ptr %l, ptr %sel_cs, double %scale)
|
||||
%sel_sl = call ptr @sel_registerName(ptr @.s_setlayer)
|
||||
%r1 = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %view, ptr %sel_sl, ptr %l)
|
||||
%sel_wl = call ptr @sel_registerName(ptr @.s_wantsl)
|
||||
%r2 = call ptr (ptr, ptr, i8) @objc_msgSend(ptr %view, ptr %sel_wl, i8 1)
|
||||
%si = fptosi double %scale to i32
|
||||
store i32 %si, ptr @W_glscale
|
||||
store ptr %l, ptr @W_mtl
|
||||
ret ptr %l
|
||||
done:
|
||||
ret ptr %have
|
||||
none:
|
||||
ret ptr null
|
||||
}
|
||||
|
||||
; ---- is the window on screen ----------------------------------------------------------------------
|
||||
; A covered or minimised window's CAMetalLayer hands out drawables at the compositor's pace (a
|
||||
; second's timeout each), so a Vulkan frame waiting for one stalled the whole game at 3 fps behind
|
||||
; another window. NSWindowOcclusionStateVisible is bit 1 of -occlusionState.
|
||||
; win_visible() -> 1 | 0 (1 with no window)
|
||||
@.s_occl = private unnamed_addr constant [15 x i8] c"occlusionState\00"
|
||||
define i32 @win_visible() {
|
||||
entry:
|
||||
%win = load ptr, ptr @W_win
|
||||
%nowin = icmp eq ptr %win, null
|
||||
br i1 %nowin, label %yes, label %ask
|
||||
ask:
|
||||
%sel = call ptr @sel_registerName(ptr @.s_occl)
|
||||
%st = call i64 (ptr, ptr) @objc_msgSend(ptr %win, ptr %sel)
|
||||
%bit = and i64 %st, 2
|
||||
%vis = icmp ne i64 %bit, 0
|
||||
%r = zext i1 %vis to i32
|
||||
ret i32 %r
|
||||
yes:
|
||||
ret i32 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -398,6 +398,15 @@ define weak ptr @win_native_instance() {
|
|||
entry:
|
||||
ret ptr null
|
||||
}
|
||||
define weak ptr @win_metal_layer() {
|
||||
entry:
|
||||
ret ptr null
|
||||
}
|
||||
; the window is always drawn to here (cocoa.ll asks macOS whether it is covered)
|
||||
define weak i32 @win_visible() {
|
||||
entry:
|
||||
ret i32 1
|
||||
}
|
||||
; the drawable's size, for a graphics API that follows the window without a GL context (render3d's
|
||||
; Vulkan resize check); headless there is no window, so 0 x 0. cocoa.ll's real one wins when linked.
|
||||
; win_set_title: cocoa.ll's in a windowed build; nothing to retitle headless
|
||||
|
|
|
|||
|
|
@ -52,6 +52,16 @@ define ptr @win_native_instance() {
|
|||
entry:
|
||||
ret ptr null
|
||||
}
|
||||
; macOS only (cocoa.ll): the CAMetalLayer MoltenVK presents to
|
||||
define ptr @win_metal_layer() {
|
||||
entry:
|
||||
ret ptr null
|
||||
}
|
||||
; the window is always drawn to here (cocoa.ll asks macOS whether it is covered)
|
||||
define i32 @win_visible() {
|
||||
entry:
|
||||
ret i32 1
|
||||
}
|
||||
|
||||
define void @win_set_title(ptr %title) {
|
||||
entry:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
; macOS has no Vulkan of its own: the LunarG loader and MoltenVK (Vulkan over
|
||||
; Metal) come with the Vulkan SDK or with a game that bundles them. So the loader
|
||||
; is looked for at run time - beside the executable, in the usual library
|
||||
; directories, then under $VULKAN_SDK - and a Mac without it gets 0 from vk_open
|
||||
; and stays on OpenGL. The contract is vk_win.ll's:
|
||||
; directories, then MoltenVK on its own (a bundle's Contents/Frameworks), then under
|
||||
; $VULKAN_SDK - and a Mac without any gets 0 from vk_open and stays on OpenGL. The contract is vk_win.ll's:
|
||||
;
|
||||
; lvk_open() -> 1 | 0 lvk_sym(name) -> ptr lvk_has(name) -> 1 | 0
|
||||
;
|
||||
|
|
@ -25,6 +25,10 @@ declare i32 @lvk_bind()
|
|||
@.lvk_c3 = private unnamed_addr constant [36 x i8] c"/opt/homebrew/lib/libvulkan.1.dylib\00"
|
||||
@.lvk_env = private unnamed_addr constant [11 x i8] c"VULKAN_SDK\00"
|
||||
@.lvk_fmt = private unnamed_addr constant [25 x i8] c"%s/lib/libvulkan.1.dylib\00"
|
||||
@.lvk_fmtm = private unnamed_addr constant [25 x i8] c"%s/lib/libMoltenVK.dylib\00"
|
||||
@.lvk_m0 = private unnamed_addr constant [49 x i8] c"@executable_path/../Frameworks/libMoltenVK.dylib\00"
|
||||
@.lvk_m1 = private unnamed_addr constant [35 x i8] c"@executable_path/libMoltenVK.dylib\00"
|
||||
@.lvk_m2 = private unnamed_addr constant [18 x i8] c"libMoltenVK.dylib\00"
|
||||
@lvk_lib = internal global ptr null
|
||||
|
||||
; RTLD_NOW (2) | RTLD_LOCAL (4)
|
||||
|
|
@ -34,52 +38,51 @@ entry:
|
|||
ret ptr %h
|
||||
}
|
||||
|
||||
; the candidates in order: a Vulkan loader first (the SDK's, so the validation layer can be
|
||||
; stacked in development), then MoltenVK itself, which exports every vk* entry point and is what
|
||||
; a bundle ships in Contents/Frameworks - no loader, no ICD json.
|
||||
@lvk_paths = internal constant [7 x ptr] [ptr @.lvk_c0, ptr @.lvk_c1, ptr @.lvk_c2, ptr @.lvk_c3, ptr @.lvk_m0, ptr @.lvk_m1, ptr @.lvk_m2]
|
||||
|
||||
define i32 @lvk_open() {
|
||||
entry:
|
||||
%have = load ptr, ptr @lvk_lib
|
||||
%open = icmp ne ptr %have, null
|
||||
br i1 %open, label %ok, label %t0
|
||||
t0:
|
||||
%h0 = call ptr @lvk_try(ptr @.lvk_c0)
|
||||
%n0 = icmp eq ptr %h0, null
|
||||
br i1 %n0, label %t1, label %got0
|
||||
got0:
|
||||
store ptr %h0, ptr @lvk_lib
|
||||
br i1 %open, label %ok, label %loop
|
||||
loop:
|
||||
%i = phi i32 [ 0, %entry ], [ %i1, %next ]
|
||||
%slot = getelementptr [7 x ptr], ptr @lvk_paths, i32 0, i32 %i
|
||||
%path = load ptr, ptr %slot
|
||||
%h = call ptr @lvk_try(ptr %path)
|
||||
%miss = icmp eq ptr %h, null
|
||||
br i1 %miss, label %next, label %got
|
||||
next:
|
||||
%i1 = add i32 %i, 1
|
||||
%more = icmp slt i32 %i1, 7
|
||||
br i1 %more, label %loop, label %sdk
|
||||
got:
|
||||
store ptr %h, ptr @lvk_lib
|
||||
br label %bind
|
||||
t1:
|
||||
%h1 = call ptr @lvk_try(ptr @.lvk_c1)
|
||||
%n1 = icmp eq ptr %h1, null
|
||||
br i1 %n1, label %t2, label %got1
|
||||
got1:
|
||||
store ptr %h1, ptr @lvk_lib
|
||||
br label %bind
|
||||
t2:
|
||||
%h2 = call ptr @lvk_try(ptr @.lvk_c2)
|
||||
%n2 = icmp eq ptr %h2, null
|
||||
br i1 %n2, label %t3, label %got2
|
||||
got2:
|
||||
store ptr %h2, ptr @lvk_lib
|
||||
br label %bind
|
||||
t3:
|
||||
%h3 = call ptr @lvk_try(ptr @.lvk_c3)
|
||||
%n3 = icmp eq ptr %h3, null
|
||||
br i1 %n3, label %t4, label %got3
|
||||
got3:
|
||||
store ptr %h3, ptr @lvk_lib
|
||||
br label %bind
|
||||
t4:
|
||||
%sdk = call ptr @getenv(ptr @.lvk_env)
|
||||
%nosdk = icmp eq ptr %sdk, null
|
||||
br i1 %nosdk, label %fail, label %t4b
|
||||
t4b:
|
||||
sdk:
|
||||
%sdkp = call ptr @getenv(ptr @.lvk_env)
|
||||
%nosdk = icmp eq ptr %sdkp, null
|
||||
br i1 %nosdk, label %fail, label %s1
|
||||
s1:
|
||||
%buf = alloca [1024 x i8]
|
||||
%w = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buf, i64 1024, ptr @.lvk_fmt, ptr %sdk)
|
||||
%w = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buf, i64 1024, ptr @.lvk_fmt, ptr %sdkp)
|
||||
%h4 = call ptr @lvk_try(ptr %buf)
|
||||
%n4 = icmp eq ptr %h4, null
|
||||
br i1 %n4, label %fail, label %got4
|
||||
br i1 %n4, label %s2, label %got4
|
||||
s2:
|
||||
%w2 = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buf, i64 1024, ptr @.lvk_fmtm, ptr %sdkp)
|
||||
%h5 = call ptr @lvk_try(ptr %buf)
|
||||
%n5 = icmp eq ptr %h5, null
|
||||
br i1 %n5, label %fail, label %got5
|
||||
got4:
|
||||
store ptr %h4, ptr @lvk_lib
|
||||
br label %bind
|
||||
got5:
|
||||
store ptr %h5, ptr @lvk_lib
|
||||
br label %bind
|
||||
bind:
|
||||
%n = call i32 @lvk_bind()
|
||||
br label %ok
|
||||
|
|
|
|||
|
|
@ -547,6 +547,16 @@ entry:
|
|||
%inst = call ptr @GetModuleHandleA(ptr null)
|
||||
ret ptr %inst
|
||||
}
|
||||
; macOS only (cocoa.ll): the CAMetalLayer MoltenVK presents to
|
||||
define ptr @win_metal_layer() {
|
||||
entry:
|
||||
ret ptr null
|
||||
}
|
||||
; the window is always drawn to here (cocoa.ll asks macOS whether it is covered)
|
||||
define i32 @win_visible() {
|
||||
entry:
|
||||
ret i32 1
|
||||
}
|
||||
|
||||
; set (%on != 0) or clear a key's bit in the 256-bit held set
|
||||
define void @win_held_bit(i32 %k, i32 %on) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue