diff --git a/changes/near-fade-spirv.md b/changes/near-fade-spirv.md new file mode 100644 index 00000000..fd4161e3 --- /dev/null +++ b/changes/near-fade-spirv.md @@ -0,0 +1,4 @@ +bump: patch +type: fix +**The foliage's near-fade programs have SPIR-V.** The three `NEAR_FADE` variants were missing +from `variants.list`, so on Vulkan the trees' and cards' programs failed to build. diff --git a/changes/vulkan-on-macos.md b/changes/vulkan-on-macos.md new file mode 100644 index 00000000..29c65201 --- /dev/null +++ b/changes/vulkan-on-macos.md @@ -0,0 +1,10 @@ +bump: minor +type: feature +**render3d's Vulkan renderer runs in a macOS window, through MoltenVK.** `R3D_GFX=vk` (or +`gpu_request("vulkan")`) now presents to a `CAMetalLayer` on the window (`VK_EXT_metal_surface`, +the runtime's `win_metal_layer()`), where it used to fall back to OpenGL. The loader also opens +MoltenVK on its own - `Contents/Frameworks/libMoltenVK.dylib` in a bundle, beside the executable, +or `$VULKAN_SDK/lib` - so a game needs no Vulkan SDK to ship it. A covered window is not drawn to +(its layer would hold every frame for a second), and on macOS one frame stays in flight so the CPU +runs the next tick while the GPU draws. OpenGL stays the default: on an M4 Pro the Vulkan frame is +still about a third slower, most of it in the alpha-tested foliage. diff --git a/changes/window-buffers.md b/changes/window-buffers.md new file mode 100644 index 00000000..edec5a2a --- /dev/null +++ b/changes/window-buffers.md @@ -0,0 +1,7 @@ +bump: patch +type: fix +**A windowed program's input no longer stops it on the first frame.** Since slices carry their +length, the window's built-ins (`win_held`, `win_mouse`, `win_pad`, `win_touch`, `win_text`, +`win_present`) were handed the slice's header instead of its elements, so the platform wrote the +mouse position over the length and the next read failed its bounds check. They take the elements, +as an `extern` call already did. diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index 047de807..d99143a5 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -199,6 +199,10 @@ export state Render3dState { gvk_al_spare: []int = null # ids to reuse gvk_pool: long = 0 gvk_fence: bytes = null + gvk_frame_fence: bytes = null # the presented frame still on the GPU (one frame in flight) + gvk_frame_pending: bool = false + gvk_frame_pending_cb: pointer = null + gvk_inflight: int = -1 # -1 until asked: 1 leaves the presented frame in flight gvk_has_hqr: bool = false gvk_ts_period: float = 0.0 # float bits: nanoseconds per timestamp tick gvk_qpool: long = 0 diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index c99478d1..90489d07 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -49,9 +49,10 @@ function gpu_select(render3d_st: mut Render3dState) -> int { if render3d_st.gpu_wanted == 0 { render3d_st.gpu_wanted = GPU_GL } render3d_st.gpu_kind = GPU_GL if render3d_st.gpu_wanted == GPU_VK { - # a window needs a surface, and only the Win32 one is built + # a window needs a surface: Win32 on Windows, a CAMetalLayer through MoltenVK on macOS render3d_st.gvk_want_surface = is_windowed() - if is_windowed() and Os.platform() != "windows" { render3d_st.gpu_fallback_reason = "the Vulkan window is built for Windows only" } + let os = Os.platform() + if is_windowed() and os != "windows" and os != "macos" { render3d_st.gpu_fallback_reason = `no Vulkan window is built for {os}` } else if not gvk_init(render3d_st) { render3d_st.gpu_fallback_reason = render3d_st.gvk_why } else if not gvk_manifest(render3d_st) { render3d_st.gpu_fallback_reason = "the renderer's SPIR-V manifest is missing" } else { render3d_st.gpu_kind = GPU_VK } diff --git a/packages/ludic.render3d/gpu_vk.ludic b/packages/ludic.render3d/gpu_vk.ludic index 69743591..2793f781 100644 --- a/packages/ludic.render3d/gpu_vk.ludic +++ b/packages/ludic.render3d/gpu_vk.ludic @@ -43,6 +43,11 @@ function gvk_ext_in(props: bytes, n: int, want: string) -> bool { # device with the Tier 1 floor switched on. False, with gvk_why set, if any of it is missing: # the caller stays on OpenGL. +function gvk_surface_ext() -> string { + if Os.platform() == "macos" { return VK_EXT_METAL_SURFACE_EXTENSION_NAME } + return VK_KHR_WIN32_SURFACE_EXTENSION_NAME +} + function gvk_init(render3d_st: mut Render3dState) -> bool { if render3d_st.gvk_ready { return true } gsl_boot(render3d_st) @@ -72,12 +77,13 @@ function gvk_init(render3d_st: mut Render3dState) -> bool { Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); n_iext += 1 Vk.put_i32(ici, VkInstanceCreateInfo_flags, VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR) } - # a window's surface: only the Win32 one is built, so a window elsewhere stays on OpenGL - render3d_st.gvk_has_surface = render3d_st.gvk_want_surface and gvk_ext_in(iexts, nie, VK_KHR_SURFACE_EXTENSION_NAME) and gvk_ext_in(iexts, nie, VK_KHR_WIN32_SURFACE_EXTENSION_NAME) - if render3d_st.gvk_want_surface and not render3d_st.gvk_has_surface { render3d_st.gvk_why = "no Win32 surface in this Vulkan instance"; return false } + # a window's surface: Win32 on Windows, Metal (MoltenVK) on macOS + let surf_ext = gvk_surface_ext() + render3d_st.gvk_has_surface = render3d_st.gvk_want_surface and gvk_ext_in(iexts, nie, VK_KHR_SURFACE_EXTENSION_NAME) and gvk_ext_in(iexts, nie, surf_ext) + if render3d_st.gvk_want_surface and not render3d_st.gvk_has_surface { render3d_st.gvk_why = `no {surf_ext} in this Vulkan instance`; return false } if render3d_st.gvk_has_surface { Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_SURFACE_EXTENSION_NAME); n_iext += 1 - Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_WIN32_SURFACE_EXTENSION_NAME); n_iext += 1 + Vk.put_ptr(iext_names, n_iext * 8, surf_ext); n_iext += 1 } # HDR output: an instance only lists the HDR colour spaces when it asks for them render3d_st.gvk_has_colorspace = render3d_st.gvk_has_surface and gvk_ext_in(iexts, nie, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME) @@ -488,6 +494,9 @@ function gvk_cmd_init(render3d_st: mut Render3dState) -> bool { render3d_st.gvk_fence = bytes(8) r = Vk.create_fence(render3d_st.gvk_dev, fci, null, render3d_st.gvk_fence) if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateFence", r) } + render3d_st.gvk_frame_fence = bytes(8) + r = Vk.create_fence(render3d_st.gvk_dev, fci, null, render3d_st.gvk_frame_fence) + if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateFence", r) } return true } # a command buffer, begun @@ -510,6 +519,7 @@ function gvk_once_begin(render3d_st: Render3dState) -> pointer { } # end it, submit it, wait for it, free it function gvk_once_end(render3d_st: mut Render3dState, cb: pointer) -> bool { + gvk_frame_wait(render3d_st) var r = Vk.end_command_buffer(cb) if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkEndCommandBuffer", r) } let cbs = bytes(8) @@ -529,12 +539,61 @@ function gvk_once_end(render3d_st: mut Render3dState, cb: pointer) -> bool { return true } +# ---- one frame in flight ------------------------------------------------------------------- +# A window's frame is submitted and presented without waiting, so the game's next tick runs on the +# CPU while the GPU draws: waiting at the present serialised the two, which cost MoltenVK a third +# of its frame rate against OpenGL. Anything that would reuse what that frame reads - the next +# frame's command buffer and ring, a one-off submit, a descriptor pool reset, a swapchain rebuild - +# waits for it first (gvk_frame_wait), and a buffer it reads is busy (gvk_buf_busy). On by default +# on macOS; R3D_VK_INFLIGHT=0/1 says otherwise. +function gvk_inflight_on(render3d_st: mut Render3dState) -> bool { + if render3d_st.gvk_inflight < 0 { + render3d_st.gvk_inflight = 0 + if Os.platform() == "macos" { render3d_st.gvk_inflight = 1 } + if r3d_env_has(render3d_st, "R3D_VK_INFLIGHT") { render3d_st.gvk_inflight = Text.to_int(r3d_env(render3d_st, "R3D_VK_INFLIGHT")) } + } + return render3d_st.gvk_inflight == 1 +} + +function gvk_frame_submit(render3d_st: mut Render3dState, cb: pointer) -> bool { + gvk_frame_wait(render3d_st) + var r = Vk.end_command_buffer(cb) + if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkEndCommandBuffer", r) } + let cbs = bytes(8) + Vk.put_ptr(cbs, 0, cb) + Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_frame_fence) + let si = bytes(VkSubmitInfo_sizeof) + Vk.zero(si, VkSubmitInfo_sizeof) + Vk.put_i32(si, VkSubmitInfo_sType, VK_STRUCTURE_TYPE_SUBMIT_INFO) + Vk.put_i32(si, VkSubmitInfo_commandBufferCount, 1) + Vk.put_ptr(si, VkSubmitInfo_pCommandBuffers, cbs) + r = Vk.queue_submit(render3d_st.gvk_queue, 1, si, Vk.get_i64(render3d_st.gvk_frame_fence, 0)) + if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkQueueSubmit", r) } + render3d_st.gvk_frame_pending = true + render3d_st.gvk_frame_pending_cb = cb + return true +} + +function gvk_frame_wait(render3d_st: mut Render3dState) -> void { + if not render3d_st.gvk_frame_pending { return } + let forever: long = -1 + Vk.wait_for_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_frame_fence, 1, forever) + let cbs = bytes(8) + Vk.put_ptr(cbs, 0, render3d_st.gvk_frame_pending_cb) + Vk.free_command_buffers(render3d_st.gvk_dev, render3d_st.gvk_pool, 1, cbs) + render3d_st.gvk_frame_pending = false + render3d_st.gvk_frame_pending_cb = null + gvk_retire_flush(render3d_st) +} + # ---- teardown ----------------------------------------------------------------------------- function gvk_shutdown(render3d_st: mut Render3dState) -> void { if not render3d_st.gvk_ready { return } + gvk_frame_wait(render3d_st) Vk.device_wait_idle(render3d_st.gvk_dev) gsl_shutdown(render3d_st) Vk.destroy_fence(render3d_st.gvk_dev, Vk.get_i64(render3d_st.gvk_fence, 0), null) + Vk.destroy_fence(render3d_st.gvk_dev, Vk.get_i64(render3d_st.gvk_frame_fence, 0), null) Vk.destroy_command_pool(render3d_st.gvk_dev, render3d_st.gvk_pool, null) Vk.destroy_device(render3d_st.gvk_dev, null) Vk.destroy_instance(render3d_st.gvk_inst, null) diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index aaf0f55e..8efb6e85 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -729,6 +729,7 @@ function gvk_sc_clear(render3d_st: mut Render3dState) -> void { # the pool is full: finish the frame recorded so far (it may use sets from it), then start again function gvk_kpool_reset(render3d_st: mut Render3dState) -> void { gvk_flush(render3d_st) + gvk_frame_wait(render3d_st) Vk.reset_descriptor_pool(render3d_st.gvk_dev, render3d_st.gvk_kpool, 0) gvk_sc_clear(render3d_st) } @@ -953,6 +954,7 @@ function gvk_screen_make(render3d_st: mut Render3dState, w: int, h: int) -> bool function gvk_frame_cb(render3d_st: mut Render3dState) -> pointer { if render3d_st.gvk_cb == null { + gvk_frame_wait(render3d_st) gvk_frame_reset(render3d_st) render3d_st.gvk_cb = gvk_once_begin(render3d_st) } @@ -1279,7 +1281,13 @@ function gvk_flush(render3d_st: mut Render3dState) -> void { # - and that image is presented. function gvk_present(render3d_st: mut Render3dState) -> void { gvk_prof_frame(render3d_st) - if render3d_st.gvk_swap != 0 { gvk_present_window(render3d_st); render3d_st.gvk_frame_no += 1; gvk_retire_flush(render3d_st); return } + if render3d_st.gvk_swap != 0 { + gvk_present_window(render3d_st) + render3d_st.gvk_frame_no += 1 + # what the frame in flight retired goes when it is done (gvk_frame_wait) + if not render3d_st.gvk_frame_pending { gvk_retire_flush(render3d_st) } + return + } if render3d_st.gvk_cb == null { return } gvk_pass_end(render3d_st) gvk_once_end(render3d_st, render3d_st.gvk_cb) @@ -1332,6 +1340,8 @@ function gvk_open(render3d_st: mut Render3dState, w: int, h: int, title: string) if is_windowed() { win_open(w, h, 1, title) # the window exists before main: this retitles it win_gl_resize(w, h) + # macOS: the Metal layer sets the backing scale, so it exists before the drawable is measured + if Os.platform() == "macos" and win_metal_layer() == null { render3d_st.gvk_why = "no Metal layer on the window"; return false } if render3d_st.gvk_size_buf == null { render3d_st.gvk_size_buf = words(4) } win_gl_drawable(render3d_st.gvk_size_buf) if render3d_st.gvk_size_buf[0] > 0 and render3d_st.gvk_size_buf[1] > 0 { gl_set_drawable(render3d_st.gvk_size_buf[0], render3d_st.gvk_size_buf[1]) } @@ -1519,13 +1529,24 @@ function gvk_read_screen(render3d_st: mut Render3dState, w: int, h: int, out: po } # ---- the window: surface and swapchain ---------------------------------------------------------- -# Windows only for now (the Win32 surface); gpu_select keeps a window elsewhere on OpenGL. +# The Win32 surface on Windows; on macOS a CAMetalLayer the runtime hangs off the view (MoltenVK). extern function win_native_window() -> pointer = "win_native_window" extern function win_native_instance() -> pointer = "win_native_instance" +extern function win_metal_layer() -> pointer = "win_metal_layer" +extern function win_visible() -> int = "win_visible" - -function gvk_surface_make(render3d_st: mut Render3dState) -> bool { - if render3d_st.gvk_surface != 0 { return true } +function gvk_surface_os(render3d_st: mut Render3dState, out: pointer) -> bool { + if Os.platform() == "macos" { + let layer = win_metal_layer() + if layer == null { render3d_st.gvk_why = "no Metal layer on the window"; return false } + let mci = bytes(VkMetalSurfaceCreateInfoEXT_sizeof) + Vk.zero(mci, VkMetalSurfaceCreateInfoEXT_sizeof) + Vk.put_i32(mci, VkMetalSurfaceCreateInfoEXT_sType, VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT) + Vk.put_ptr(mci, VkMetalSurfaceCreateInfoEXT_pLayer, layer) + let rm = Vk.create_metal_surface_ext(render3d_st.gvk_inst, mci, null, out) + if rm != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateMetalSurfaceEXT", rm) } + return true + } let hwnd = win_native_window() if hwnd == null { render3d_st.gvk_why = "no window to present to"; return false } let sci = bytes(VkWin32SurfaceCreateInfoKHR_sizeof) @@ -1533,9 +1554,15 @@ function gvk_surface_make(render3d_st: mut Render3dState) -> bool { Vk.put_i32(sci, VkWin32SurfaceCreateInfoKHR_sType, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR) Vk.put_ptr(sci, VkWin32SurfaceCreateInfoKHR_hinstance, win_native_instance()) Vk.put_ptr(sci, VkWin32SurfaceCreateInfoKHR_hwnd, hwnd) - let out = bytes(8) let r = Vk.create_win32_surface_khr(render3d_st.gvk_inst, sci, null, out) if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateWin32SurfaceKHR", r) } + return true +} + +function gvk_surface_make(render3d_st: mut Render3dState) -> bool { + if render3d_st.gvk_surface != 0 { return true } + let out = bytes(8) + if not gvk_surface_os(render3d_st, out) { return false } render3d_st.gvk_surface = gvk_handle(out) let ok = bytes(4) Vk.put_i32(ok, 0, 0) @@ -1551,6 +1578,7 @@ function gvk_surface_make(render3d_st: mut Render3dState) -> bool { # (Re)make the swapchain for a w x h client area. The old one is handed over and then destroyed. function gvk_swap_make(render3d_st: mut Render3dState, w: int, h: int) -> bool { if not gvk_surface_make(render3d_st) { return false } + gvk_frame_wait(render3d_st) Vk.device_wait_idle(render3d_st.gvk_dev) let caps = bytes(VkSurfaceCapabilitiesKHR_sizeof) Vk.get_physical_device_surface_capabilities_khr(render3d_st.gvk_pd, render3d_st.gvk_surface, caps) @@ -1641,6 +1669,14 @@ function gvk_swap_make(render3d_st: mut Render3dState, w: int, h: int) -> bool { function gvk_present_window(render3d_st: mut Render3dState) -> void { let cb = gvk_frame_cb(render3d_st) gvk_pass_end(render3d_st) + # a covered window is not drawn to: its layer would hold the frame for a drawable the compositor + # hands back once a second. The frame still runs, paced at about 60 Hz, so the game keeps time. + if win_visible() == 0 { + if gvk_inflight_on(render3d_st) { gvk_frame_submit(render3d_st, cb) } else { gvk_once_end(render3d_st, cb) } + render3d_st.gvk_cb = null + Time.sleep_us(16000) + return + } if render3d_st.gvk_swap_stale { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return } let idx = bytes(4) Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_acq_fence) @@ -1670,7 +1706,7 @@ function gvk_present_window(render3d_st: mut Render3dState) -> void { Vk.cmd_blit_image(cb, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, VK_FILTER_LINEAR) gvk_barrier(cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) gvk_barrier(cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) - gvk_once_end(render3d_st, cb) + if gvk_inflight_on(render3d_st) { gvk_frame_submit(render3d_st, cb) } else { gvk_once_end(render3d_st, cb) } render3d_st.gvk_cb = null let pi = bytes(VkPresentInfoKHR_sizeof) Vk.zero(pi, VkPresentInfoKHR_sizeof) diff --git a/packages/ludic.render3d/gpu_vk_res.ludic b/packages/ludic.render3d/gpu_vk_res.ludic index e17a6f15..298cb72c 100644 --- a/packages/ludic.render3d/gpu_vk_res.ludic +++ b/packages/ludic.render3d/gpu_vk_res.ludic @@ -134,6 +134,7 @@ function gvk_staging(render3d_st: mut Render3dState, n: int, usage: int) -> poin return Vk.get_ptr(pp, 0) } function gvk_staging_free(render3d_st: mut Render3dState) -> void { + gvk_frame_wait(render3d_st) # the frame in flight may still read it Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem) Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_st_buf, null) Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem, null) @@ -142,6 +143,7 @@ function gvk_staging_free(render3d_st: mut Render3dState) -> void { # (Re)make the image behind a handle: glTexImage2D on a texture that already has one replaces it. function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool, ifmt: int, w: int, h: int, layers: int, with_mips: bool) -> bool { + gvk_frame_wait(render3d_st) # the frame in flight may still read it if tex <= 0 or tex >= len(render3d_st.gvk_tex_image) { return false } let vkfmt = gvk_format(ifmt) if vkfmt == VK_FORMAT_UNDEFINED { print(`r3d: vulkan: no image format for GL format {ifmt}`); return false } @@ -304,6 +306,7 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w: # A target made without pixels has one level; the renderer asking it for mipmaps (the exposure # measure reads the HDR scene's smallest level every frame) grows it a full chain, level 0 kept. function gvk_tex_grow_mips(render3d_st: mut Render3dState, tex: int, w: int, h: int) -> bool { + gvk_frame_wait(render3d_st) # the frame in flight may still read it let old_image = render3d_st.gvk_tex_image[tex] let old_view = render3d_st.gvk_tex_view[tex] let old_mem = render3d_st.gvk_tex_mem[tex] @@ -420,6 +423,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in } function gvk_tex_release(render3d_st: mut Render3dState, tex: int) -> void { + gvk_frame_wait(render3d_st) # the frame in flight may still read it if render3d_st.gvk_tex_image[tex] == 0 { return } let zero: long = 0 Vk.destroy_image_view(render3d_st.gvk_dev, render3d_st.gvk_tex_view[tex], null) @@ -553,8 +557,8 @@ function gvk_buf_new(render3d_st: mut Render3dState) -> int { function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void { if render3d_st.gvk_buf[b] == 0 { return } let zero: long = 0 - if render3d_st.gvk_buf_used[b] == render3d_st.gvk_frame_no { - # a draw recorded this frame still reads it: destroy it once the frame has been submitted + if gvk_buf_busy(render3d_st, b) { + # a draw recorded this frame (or the frame in flight) still reads it: destroy it once the frame has been submitted push(render3d_st.gvk_retired_buf, render3d_st.gvk_buf[b]); push(render3d_st.gvk_retired_mem, render3d_st.gvk_buf_mem[b]) } else { Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_buf[b], null) @@ -562,6 +566,12 @@ function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void { } render3d_st.gvk_buf[b] = zero; render3d_st.gvk_buf_mem[b] = zero; render3d_st.gvk_buf_size[b] = 0; render3d_st.gvk_buf_map[b] = null; render3d_st.gvk_buf_used[b] = 0 } +# read by a draw recorded this frame, or by the presented frame still on the GPU +function gvk_buf_busy(render3d_st: Render3dState, b: int) -> bool { + let u = render3d_st.gvk_buf_used[b] + return u == render3d_st.gvk_frame_no or (render3d_st.gvk_frame_pending and u == render3d_st.gvk_frame_no - 1) +} + # the frame's submitted work is done: storage retired during it can go function gvk_retire_flush(render3d_st: mut Render3dState) -> void { if render3d_st.gvk_retired_buf == null { return } @@ -587,7 +597,7 @@ function gvk_buf_is_gpu(render3d_st: Render3dState, b: int) -> bool { return ren function gvk_buf_reserve(render3d_st: mut Render3dState, b: int, n: int) -> bool { if b <= 0 or b >= len(render3d_st.gvk_buf) { return false } # already big enough, and no draw this frame reads what is there: fill it in place - if render3d_st.gvk_buf[b] != 0 and render3d_st.gvk_buf_size[b] >= n and (render3d_st.gvk_buf_used[b] != render3d_st.gvk_frame_no or gvk_buf_is_gpu(render3d_st, b)) { return true } + if render3d_st.gvk_buf[b] != 0 and render3d_st.gvk_buf_size[b] >= n and (not gvk_buf_busy(render3d_st, b) or gvk_buf_is_gpu(render3d_st, b)) { return true } gvk_buf_release(render3d_st, b) var size = n if size < 64 { size = 64 } diff --git a/packages/ludic.render3d/shaders/spv/266d2401.frag.spv b/packages/ludic.render3d/shaders/spv/266d2401.frag.spv new file mode 100644 index 00000000..ab904275 Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/266d2401.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/266d2401.vert.spv b/packages/ludic.render3d/shaders/spv/266d2401.vert.spv new file mode 100644 index 00000000..ba23f6d5 Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/266d2401.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/5e70d731.frag.spv b/packages/ludic.render3d/shaders/spv/5e70d731.frag.spv new file mode 100644 index 00000000..529200bb Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/5e70d731.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/5e70d731.vert.spv b/packages/ludic.render3d/shaders/spv/5e70d731.vert.spv new file mode 100644 index 00000000..215b17d6 Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/5e70d731.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b603a056.frag.spv b/packages/ludic.render3d/shaders/spv/b603a056.frag.spv new file mode 100644 index 00000000..3f473cc5 Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/b603a056.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b603a056.vert.spv b/packages/ludic.render3d/shaders/spv/b603a056.vert.spv new file mode 100644 index 00000000..215b17d6 Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/b603a056.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/manifest.txt b/packages/ludic.render3d/shaders/spv/manifest.txt index d0bd2869..c344f908 100644 --- a/packages/ludic.render3d/shaders/spv/manifest.txt +++ b/packages/ludic.render3d/shaders/spv/manifest.txt @@ -141,6 +141,56 @@ T c57b4f14 u_shadow 5 T c57b4f14 u_src 6 T c57b4f14 u_tershadow 7 T c57b4f14 u_ts_height 8 +P 5161e6ba fullscreen.vert dof.frag +B 5161e6ba frag 1 916 +U 5161e6ba frag u_cascade_vp 0 mat4 5 64 +U 5161e6ba frag u_cascade_split 320 float 5 16 +U 5161e6ba frag u_cascade_range 400 float 5 16 +U 5161e6ba frag u_cascade_texel 480 float 5 16 +U 5161e6ba frag u_sun_dir 560 vec3 1 0 +U 5161e6ba frag u_sun_color 576 vec3 1 0 +U 5161e6ba frag u_cam_pos 592 vec3 1 0 +U 5161e6ba frag u_prefilter_levels 604 float 1 0 +U 5161e6ba frag u_fog_density 608 float 1 0 +U 5161e6ba frag u_fog_height_falloff 612 float 1 0 +U 5161e6ba frag u_fog_base 616 float 1 0 +U 5161e6ba frag u_clip_y 620 float 1 0 +U 5161e6ba frag u_spec_scale 624 float 1 0 +U 5161e6ba frag u_sky_rot 632 vec2 1 0 +U 5161e6ba frag u_ground_alb 640 vec3 1 0 +U 5161e6ba frag u_ground_alb_hi 656 vec3 1 0 +U 5161e6ba frag u_ground_hi_y 668 float 1 0 +U 5161e6ba frag u_ground_hi_w 672 float 1 0 +U 5161e6ba frag u_ibl_scale 688 vec3 1 0 +U 5161e6ba frag u_daylight 700 float 1 0 +U 5161e6ba frag u_fire_pos 704 vec3 1 0 +U 5161e6ba frag u_fire_color 720 vec3 1 0 +U 5161e6ba frag u_hand_pos 736 vec3 1 0 +U 5161e6ba frag u_hand_color 752 vec3 1 0 +U 5161e6ba frag u_hand_dir 768 vec3 1 0 +U 5161e6ba frag u_hand_cone 780 float 1 0 +U 5161e6ba frag u_hand_reach 784 float 1 0 +U 5161e6ba frag u_ts_origin 792 vec2 1 0 +U 5161e6ba frag u_ts_half 800 float 1 0 +U 5161e6ba frag u_ts_on 804 float 1 0 +U 5161e6ba frag u_force_cascade 808 int 1 0 +U 5161e6ba frag u_cloud_shadow 812 float 1 0 +U 5161e6ba frag u_time 816 float 1 0 +U 5161e6ba frag u_fog_inscatter 820 float 1 0 +U 5161e6ba frag u_fog_desat 824 float 1 0 +U 5161e6ba frag u_inv_proj 832 mat4 1 0 +U 5161e6ba frag u_texel 896 vec2 1 0 +U 5161e6ba frag u_focus 904 float 1 0 +U 5161e6ba frag u_aperture 908 float 1 0 +U 5161e6ba frag u_max_coc 912 float 1 0 +T 5161e6ba u_brdf 2 +T 5161e6ba u_depth 3 +T 5161e6ba u_irradiance 4 +T 5161e6ba u_prefilter 5 +T 5161e6ba u_shadow 6 +T 5161e6ba u_src 7 +T 5161e6ba u_tershadow 8 +T 5161e6ba u_ts_height 9 P 4be7acd9 fullscreen.vert heightgen.frag #define DEM; B 4be7acd9 frag 1 920 U 4be7acd9 frag u_cascade_vp 0 mat4 5 64 @@ -740,6 +790,58 @@ T a4683767 u_prefilter 8 T a4683767 u_shadow 9 T a4683767 u_tershadow 10 T a4683767 u_ts_height 11 +P 74258189 fullscreen.vert volumetric.frag +B 74258189 frag 1 924 +U 74258189 frag u_cascade_vp 0 mat4 5 64 +U 74258189 frag u_cascade_split 320 float 5 16 +U 74258189 frag u_cascade_range 400 float 5 16 +U 74258189 frag u_cascade_texel 480 float 5 16 +U 74258189 frag u_sun_dir 560 vec3 1 0 +U 74258189 frag u_sun_color 576 vec3 1 0 +U 74258189 frag u_cam_pos 592 vec3 1 0 +U 74258189 frag u_prefilter_levels 604 float 1 0 +U 74258189 frag u_fog_density 608 float 1 0 +U 74258189 frag u_fog_height_falloff 612 float 1 0 +U 74258189 frag u_fog_base 616 float 1 0 +U 74258189 frag u_clip_y 620 float 1 0 +U 74258189 frag u_spec_scale 624 float 1 0 +U 74258189 frag u_sky_rot 632 vec2 1 0 +U 74258189 frag u_ground_alb 640 vec3 1 0 +U 74258189 frag u_ground_alb_hi 656 vec3 1 0 +U 74258189 frag u_ground_hi_y 668 float 1 0 +U 74258189 frag u_ground_hi_w 672 float 1 0 +U 74258189 frag u_ibl_scale 688 vec3 1 0 +U 74258189 frag u_daylight 700 float 1 0 +U 74258189 frag u_fire_pos 704 vec3 1 0 +U 74258189 frag u_fire_color 720 vec3 1 0 +U 74258189 frag u_hand_pos 736 vec3 1 0 +U 74258189 frag u_hand_color 752 vec3 1 0 +U 74258189 frag u_hand_dir 768 vec3 1 0 +U 74258189 frag u_hand_cone 780 float 1 0 +U 74258189 frag u_hand_reach 784 float 1 0 +U 74258189 frag u_ts_origin 792 vec2 1 0 +U 74258189 frag u_ts_half 800 float 1 0 +U 74258189 frag u_ts_on 804 float 1 0 +U 74258189 frag u_force_cascade 808 int 1 0 +U 74258189 frag u_cloud_shadow 812 float 1 0 +U 74258189 frag u_time 816 float 1 0 +U 74258189 frag u_fog_inscatter 820 float 1 0 +U 74258189 frag u_fog_desat 824 float 1 0 +U 74258189 frag u_inv_vp 832 mat4 1 0 +U 74258189 frag u_vol_steps 896 float 1 0 +U 74258189 frag u_vol_density 900 float 1 0 +U 74258189 frag u_vol_falloff 904 float 1 0 +U 74258189 frag u_vol_far 908 float 1 0 +U 74258189 frag u_vol_g 912 float 1 0 +U 74258189 frag u_vol_mist 916 float 1 0 +U 74258189 frag u_vol_mist_h 920 float 1 0 +T 74258189 u_brdf 2 +T 74258189 u_depth 3 +T 74258189 u_irradiance 4 +T 74258189 u_prefilter 5 +T 74258189 u_shadow 6 +T 74258189 u_tershadow 7 +T 74258189 u_ts_height 8 P d48d6a48 grass.mesh model.frag #define FOLIAGE;#define BLADE;#define MESH; B d48d6a48 vert 0 4384 U d48d6a48 vert u_view 0 mat4 1 0 @@ -1355,6 +1457,68 @@ T bfa15301 u_prefilter 5 T bfa15301 u_shadow 6 T bfa15301 u_tershadow 7 T bfa15301 u_ts_height 8 +P 266d2401 model.vert depth.frag #define FOLIAGE;#define WIND;#define ALPHA_TEST;#define NEAR_FADE; +B 266d2401 vert 0 236 +U 266d2401 vert u_view 0 mat4 1 0 +U 266d2401 vert u_proj 64 mat4 1 0 +U 266d2401 vert u_light_vp 128 mat4 1 0 +U 266d2401 vert u_time 192 float 1 0 +U 266d2401 vert u_wind 196 float 1 0 +U 266d2401 vert u_card_w 200 float 1 0 +U 266d2401 vert u_card_h 204 float 1 0 +U 266d2401 vert u_ground 208 float 1 0 +U 266d2401 vert u_ts_origin 216 vec2 1 0 +U 266d2401 vert u_ts_half 224 float 1 0 +U 266d2401 vert u_model_h 228 float 1 0 +U 266d2401 vert u_flutter 232 float 1 0 +I 266d2401 i_rot 4 vec4 +I 266d2401 a_pos 0 vec3 +I 266d2401 i_pos 3 vec4 +I 266d2401 a_nrm 1 vec3 +I 266d2401 a_uv 2 vec2 +B 266d2401 frag 1 828 +U 266d2401 frag u_cascade_vp 0 mat4 5 64 +U 266d2401 frag u_cascade_split 320 float 5 16 +U 266d2401 frag u_cascade_range 400 float 5 16 +U 266d2401 frag u_cascade_texel 480 float 5 16 +U 266d2401 frag u_sun_dir 560 vec3 1 0 +U 266d2401 frag u_sun_color 576 vec3 1 0 +U 266d2401 frag u_cam_pos 592 vec3 1 0 +U 266d2401 frag u_prefilter_levels 604 float 1 0 +U 266d2401 frag u_fog_density 608 float 1 0 +U 266d2401 frag u_fog_height_falloff 612 float 1 0 +U 266d2401 frag u_fog_base 616 float 1 0 +U 266d2401 frag u_clip_y 620 float 1 0 +U 266d2401 frag u_spec_scale 624 float 1 0 +U 266d2401 frag u_sky_rot 632 vec2 1 0 +U 266d2401 frag u_ground_alb 640 vec3 1 0 +U 266d2401 frag u_ground_alb_hi 656 vec3 1 0 +U 266d2401 frag u_ground_hi_y 668 float 1 0 +U 266d2401 frag u_ground_hi_w 672 float 1 0 +U 266d2401 frag u_ibl_scale 688 vec3 1 0 +U 266d2401 frag u_daylight 700 float 1 0 +U 266d2401 frag u_fire_pos 704 vec3 1 0 +U 266d2401 frag u_fire_color 720 vec3 1 0 +U 266d2401 frag u_hand_pos 736 vec3 1 0 +U 266d2401 frag u_hand_color 752 vec3 1 0 +U 266d2401 frag u_hand_dir 768 vec3 1 0 +U 266d2401 frag u_hand_cone 780 float 1 0 +U 266d2401 frag u_hand_reach 784 float 1 0 +U 266d2401 frag u_ts_origin 792 vec2 1 0 +U 266d2401 frag u_ts_half 800 float 1 0 +U 266d2401 frag u_ts_on 804 float 1 0 +U 266d2401 frag u_force_cascade 808 int 1 0 +U 266d2401 frag u_cloud_shadow 812 float 1 0 +U 266d2401 frag u_time 816 float 1 0 +U 266d2401 frag u_fog_inscatter 820 float 1 0 +U 266d2401 frag u_fog_desat 824 float 1 0 +T 266d2401 u_brdf 2 +T 266d2401 u_diff 3 +T 266d2401 u_irradiance 4 +T 266d2401 u_prefilter 5 +T 266d2401 u_shadow 6 +T 266d2401 u_tershadow 7 +T 266d2401 u_ts_height 8 P 01089da8 model.vert model.frag B 01089da8 vert 0 236 U 01089da8 vert u_view 0 mat4 1 0 @@ -1566,6 +1730,76 @@ T 1ce5c5d1 u_prefilter 8 T 1ce5c5d1 u_shadow 9 T 1ce5c5d1 u_tershadow 10 T 1ce5c5d1 u_ts_height 11 +P 5e70d731 model.vert model.frag #define FOLIAGE;#define WIND;#define ALPHA_TEST;#define NEAR_FADE; +B 5e70d731 vert 0 236 +U 5e70d731 vert u_view 0 mat4 1 0 +U 5e70d731 vert u_proj 64 mat4 1 0 +U 5e70d731 vert u_light_vp 128 mat4 1 0 +U 5e70d731 vert u_time 192 float 1 0 +U 5e70d731 vert u_wind 196 float 1 0 +U 5e70d731 vert u_card_w 200 float 1 0 +U 5e70d731 vert u_card_h 204 float 1 0 +U 5e70d731 vert u_ground 208 float 1 0 +U 5e70d731 vert u_ts_origin 216 vec2 1 0 +U 5e70d731 vert u_ts_half 224 float 1 0 +U 5e70d731 vert u_model_h 228 float 1 0 +U 5e70d731 vert u_flutter 232 float 1 0 +I 5e70d731 i_rot 4 vec4 +I 5e70d731 a_pos 0 vec3 +I 5e70d731 i_pos 3 vec4 +I 5e70d731 a_nrm 1 vec3 +I 5e70d731 a_uv 2 vec2 +B 5e70d731 frag 1 916 +U 5e70d731 frag u_cascade_vp 0 mat4 5 64 +U 5e70d731 frag u_cascade_split 320 float 5 16 +U 5e70d731 frag u_cascade_range 400 float 5 16 +U 5e70d731 frag u_cascade_texel 480 float 5 16 +U 5e70d731 frag u_sun_dir 560 vec3 1 0 +U 5e70d731 frag u_sun_color 576 vec3 1 0 +U 5e70d731 frag u_cam_pos 592 vec3 1 0 +U 5e70d731 frag u_prefilter_levels 604 float 1 0 +U 5e70d731 frag u_fog_density 608 float 1 0 +U 5e70d731 frag u_fog_height_falloff 612 float 1 0 +U 5e70d731 frag u_fog_base 616 float 1 0 +U 5e70d731 frag u_clip_y 620 float 1 0 +U 5e70d731 frag u_spec_scale 624 float 1 0 +U 5e70d731 frag u_sky_rot 632 vec2 1 0 +U 5e70d731 frag u_ground_alb 640 vec3 1 0 +U 5e70d731 frag u_ground_alb_hi 656 vec3 1 0 +U 5e70d731 frag u_ground_hi_y 668 float 1 0 +U 5e70d731 frag u_ground_hi_w 672 float 1 0 +U 5e70d731 frag u_ibl_scale 688 vec3 1 0 +U 5e70d731 frag u_daylight 700 float 1 0 +U 5e70d731 frag u_fire_pos 704 vec3 1 0 +U 5e70d731 frag u_fire_color 720 vec3 1 0 +U 5e70d731 frag u_hand_pos 736 vec3 1 0 +U 5e70d731 frag u_hand_color 752 vec3 1 0 +U 5e70d731 frag u_hand_dir 768 vec3 1 0 +U 5e70d731 frag u_hand_cone 780 float 1 0 +U 5e70d731 frag u_hand_reach 784 float 1 0 +U 5e70d731 frag u_ts_origin 792 vec2 1 0 +U 5e70d731 frag u_ts_half 800 float 1 0 +U 5e70d731 frag u_ts_on 804 float 1 0 +U 5e70d731 frag u_force_cascade 808 int 1 0 +U 5e70d731 frag u_cloud_shadow 812 float 1 0 +U 5e70d731 frag u_time 816 float 1 0 +U 5e70d731 frag u_fog_inscatter 820 float 1 0 +U 5e70d731 frag u_fog_desat 824 float 1 0 +U 5e70d731 frag u_model_h 828 float 1 0 +U 5e70d731 frag u_view 832 mat4 1 0 +U 5e70d731 frag u_tint 896 vec3 1 0 +U 5e70d731 frag u_rough_scale 908 float 1 0 +U 5e70d731 frag u_emissive 912 float 1 0 +T 5e70d731 u_arm 2 +T 5e70d731 u_blade_tex 3 +T 5e70d731 u_brdf 4 +T 5e70d731 u_diff 5 +T 5e70d731 u_irradiance 6 +T 5e70d731 u_nrm 7 +T 5e70d731 u_prefilter 8 +T 5e70d731 u_shadow 9 +T 5e70d731 u_tershadow 10 +T 5e70d731 u_ts_height 11 P f4a2b586 model.vert model.frag #define FOLIAGE;#define WIND;#define BLADE; B f4a2b586 vert 0 252 U f4a2b586 vert u_view 0 mat4 1 0 @@ -1783,6 +2017,76 @@ T 4dda406a u_prefilter 8 T 4dda406a u_shadow 9 T 4dda406a u_tershadow 10 T 4dda406a u_ts_height 11 +P b603a056 model.vert model.frag #define FOLIAGE;#define WIND;#define EQ_PASS;#define NEAR_FADE; +B b603a056 vert 0 236 +U b603a056 vert u_view 0 mat4 1 0 +U b603a056 vert u_proj 64 mat4 1 0 +U b603a056 vert u_light_vp 128 mat4 1 0 +U b603a056 vert u_time 192 float 1 0 +U b603a056 vert u_wind 196 float 1 0 +U b603a056 vert u_card_w 200 float 1 0 +U b603a056 vert u_card_h 204 float 1 0 +U b603a056 vert u_ground 208 float 1 0 +U b603a056 vert u_ts_origin 216 vec2 1 0 +U b603a056 vert u_ts_half 224 float 1 0 +U b603a056 vert u_model_h 228 float 1 0 +U b603a056 vert u_flutter 232 float 1 0 +I b603a056 i_rot 4 vec4 +I b603a056 a_pos 0 vec3 +I b603a056 i_pos 3 vec4 +I b603a056 a_nrm 1 vec3 +I b603a056 a_uv 2 vec2 +B b603a056 frag 1 916 +U b603a056 frag u_cascade_vp 0 mat4 5 64 +U b603a056 frag u_cascade_split 320 float 5 16 +U b603a056 frag u_cascade_range 400 float 5 16 +U b603a056 frag u_cascade_texel 480 float 5 16 +U b603a056 frag u_sun_dir 560 vec3 1 0 +U b603a056 frag u_sun_color 576 vec3 1 0 +U b603a056 frag u_cam_pos 592 vec3 1 0 +U b603a056 frag u_prefilter_levels 604 float 1 0 +U b603a056 frag u_fog_density 608 float 1 0 +U b603a056 frag u_fog_height_falloff 612 float 1 0 +U b603a056 frag u_fog_base 616 float 1 0 +U b603a056 frag u_clip_y 620 float 1 0 +U b603a056 frag u_spec_scale 624 float 1 0 +U b603a056 frag u_sky_rot 632 vec2 1 0 +U b603a056 frag u_ground_alb 640 vec3 1 0 +U b603a056 frag u_ground_alb_hi 656 vec3 1 0 +U b603a056 frag u_ground_hi_y 668 float 1 0 +U b603a056 frag u_ground_hi_w 672 float 1 0 +U b603a056 frag u_ibl_scale 688 vec3 1 0 +U b603a056 frag u_daylight 700 float 1 0 +U b603a056 frag u_fire_pos 704 vec3 1 0 +U b603a056 frag u_fire_color 720 vec3 1 0 +U b603a056 frag u_hand_pos 736 vec3 1 0 +U b603a056 frag u_hand_color 752 vec3 1 0 +U b603a056 frag u_hand_dir 768 vec3 1 0 +U b603a056 frag u_hand_cone 780 float 1 0 +U b603a056 frag u_hand_reach 784 float 1 0 +U b603a056 frag u_ts_origin 792 vec2 1 0 +U b603a056 frag u_ts_half 800 float 1 0 +U b603a056 frag u_ts_on 804 float 1 0 +U b603a056 frag u_force_cascade 808 int 1 0 +U b603a056 frag u_cloud_shadow 812 float 1 0 +U b603a056 frag u_time 816 float 1 0 +U b603a056 frag u_fog_inscatter 820 float 1 0 +U b603a056 frag u_fog_desat 824 float 1 0 +U b603a056 frag u_model_h 828 float 1 0 +U b603a056 frag u_view 832 mat4 1 0 +U b603a056 frag u_tint 896 vec3 1 0 +U b603a056 frag u_rough_scale 908 float 1 0 +U b603a056 frag u_emissive 912 float 1 0 +T b603a056 u_arm 2 +T b603a056 u_blade_tex 3 +T b603a056 u_brdf 4 +T b603a056 u_diff 5 +T b603a056 u_irradiance 6 +T b603a056 u_nrm 7 +T b603a056 u_prefilter 8 +T b603a056 u_shadow 9 +T b603a056 u_tershadow 10 +T b603a056 u_ts_height 11 P 3b183d8f model.vert model.frag #define FOLIAGE;#define WIND;#define FLOWER; B 3b183d8f vert 0 236 U 3b183d8f vert u_view 0 mat4 1 0 @@ -3218,105 +3522,3 @@ T 46e2325c u_scene 7 T 46e2325c u_shadow 8 T 46e2325c u_tershadow 9 T 46e2325c u_ts_height 10 -P 74258189 fullscreen.vert volumetric.frag -B 74258189 frag 1 924 -U 74258189 frag u_cascade_vp 0 mat4 5 64 -U 74258189 frag u_cascade_split 320 float 5 16 -U 74258189 frag u_cascade_range 400 float 5 16 -U 74258189 frag u_cascade_texel 480 float 5 16 -U 74258189 frag u_sun_dir 560 vec3 1 0 -U 74258189 frag u_sun_color 576 vec3 1 0 -U 74258189 frag u_cam_pos 592 vec3 1 0 -U 74258189 frag u_prefilter_levels 604 float 1 0 -U 74258189 frag u_fog_density 608 float 1 0 -U 74258189 frag u_fog_height_falloff 612 float 1 0 -U 74258189 frag u_fog_base 616 float 1 0 -U 74258189 frag u_clip_y 620 float 1 0 -U 74258189 frag u_spec_scale 624 float 1 0 -U 74258189 frag u_sky_rot 632 vec2 1 0 -U 74258189 frag u_ground_alb 640 vec3 1 0 -U 74258189 frag u_ground_alb_hi 656 vec3 1 0 -U 74258189 frag u_ground_hi_y 668 float 1 0 -U 74258189 frag u_ground_hi_w 672 float 1 0 -U 74258189 frag u_ibl_scale 688 vec3 1 0 -U 74258189 frag u_daylight 700 float 1 0 -U 74258189 frag u_fire_pos 704 vec3 1 0 -U 74258189 frag u_fire_color 720 vec3 1 0 -U 74258189 frag u_hand_pos 736 vec3 1 0 -U 74258189 frag u_hand_color 752 vec3 1 0 -U 74258189 frag u_hand_dir 768 vec3 1 0 -U 74258189 frag u_hand_cone 780 float 1 0 -U 74258189 frag u_hand_reach 784 float 1 0 -U 74258189 frag u_ts_origin 792 vec2 1 0 -U 74258189 frag u_ts_half 800 float 1 0 -U 74258189 frag u_ts_on 804 float 1 0 -U 74258189 frag u_force_cascade 808 int 1 0 -U 74258189 frag u_cloud_shadow 812 float 1 0 -U 74258189 frag u_time 816 float 1 0 -U 74258189 frag u_fog_inscatter 820 float 1 0 -U 74258189 frag u_fog_desat 824 float 1 0 -U 74258189 frag u_inv_vp 832 mat4 1 0 -U 74258189 frag u_vol_steps 896 float 1 0 -U 74258189 frag u_vol_density 900 float 1 0 -U 74258189 frag u_vol_falloff 904 float 1 0 -U 74258189 frag u_vol_far 908 float 1 0 -U 74258189 frag u_vol_g 912 float 1 0 -U 74258189 frag u_vol_mist 916 float 1 0 -U 74258189 frag u_vol_mist_h 920 float 1 0 -T 74258189 u_brdf 2 -T 74258189 u_depth 3 -T 74258189 u_irradiance 4 -T 74258189 u_prefilter 5 -T 74258189 u_shadow 6 -T 74258189 u_tershadow 7 -T 74258189 u_ts_height 8 -P 5161e6ba fullscreen.vert dof.frag -B 5161e6ba frag 1 916 -U 5161e6ba frag u_cascade_vp 0 mat4 5 64 -U 5161e6ba frag u_cascade_split 320 float 5 16 -U 5161e6ba frag u_cascade_range 400 float 5 16 -U 5161e6ba frag u_cascade_texel 480 float 5 16 -U 5161e6ba frag u_sun_dir 560 vec3 1 0 -U 5161e6ba frag u_sun_color 576 vec3 1 0 -U 5161e6ba frag u_cam_pos 592 vec3 1 0 -U 5161e6ba frag u_prefilter_levels 604 float 1 0 -U 5161e6ba frag u_fog_density 608 float 1 0 -U 5161e6ba frag u_fog_height_falloff 612 float 1 0 -U 5161e6ba frag u_fog_base 616 float 1 0 -U 5161e6ba frag u_clip_y 620 float 1 0 -U 5161e6ba frag u_spec_scale 624 float 1 0 -U 5161e6ba frag u_sky_rot 632 vec2 1 0 -U 5161e6ba frag u_ground_alb 640 vec3 1 0 -U 5161e6ba frag u_ground_alb_hi 656 vec3 1 0 -U 5161e6ba frag u_ground_hi_y 668 float 1 0 -U 5161e6ba frag u_ground_hi_w 672 float 1 0 -U 5161e6ba frag u_ibl_scale 688 vec3 1 0 -U 5161e6ba frag u_daylight 700 float 1 0 -U 5161e6ba frag u_fire_pos 704 vec3 1 0 -U 5161e6ba frag u_fire_color 720 vec3 1 0 -U 5161e6ba frag u_hand_pos 736 vec3 1 0 -U 5161e6ba frag u_hand_color 752 vec3 1 0 -U 5161e6ba frag u_hand_dir 768 vec3 1 0 -U 5161e6ba frag u_hand_cone 780 float 1 0 -U 5161e6ba frag u_hand_reach 784 float 1 0 -U 5161e6ba frag u_ts_origin 792 vec2 1 0 -U 5161e6ba frag u_ts_half 800 float 1 0 -U 5161e6ba frag u_ts_on 804 float 1 0 -U 5161e6ba frag u_force_cascade 808 int 1 0 -U 5161e6ba frag u_cloud_shadow 812 float 1 0 -U 5161e6ba frag u_time 816 float 1 0 -U 5161e6ba frag u_fog_inscatter 820 float 1 0 -U 5161e6ba frag u_fog_desat 824 float 1 0 -U 5161e6ba frag u_inv_proj 832 mat4 1 0 -U 5161e6ba frag u_texel 896 vec2 1 0 -U 5161e6ba frag u_focus 904 float 1 0 -U 5161e6ba frag u_aperture 908 float 1 0 -U 5161e6ba frag u_max_coc 912 float 1 0 -T 5161e6ba u_brdf 2 -T 5161e6ba u_depth 3 -T 5161e6ba u_irradiance 4 -T 5161e6ba u_prefilter 5 -T 5161e6ba u_shadow 6 -T 5161e6ba u_src 7 -T 5161e6ba u_tershadow 8 -T 5161e6ba u_ts_height 9 diff --git a/packages/ludic.render3d/shaders/variants.list b/packages/ludic.render3d/shaders/variants.list index fad4fc28..fee8e402 100644 --- a/packages/ludic.render3d/shaders/variants.list +++ b/packages/ludic.render3d/shaders/variants.list @@ -5,6 +5,7 @@ fullscreen.vert|adapt.frag| fullscreen.vert|bloom_down.frag| fullscreen.vert|bloom_up.frag| +fullscreen.vert|dof.frag| fullscreen.vert|heightgen.frag|#define DEM; fullscreen.vert|ibl_brdf.frag| fullscreen.vert|ibl_irradiance.frag| @@ -17,6 +18,7 @@ fullscreen.vert|ternormal.frag| fullscreen.vert|tershadow.frag|#define NOISE_ONLY; fullscreen.vert|tonemap.frag| fullscreen.vert|tonemap.frag|#define HDR10; +fullscreen.vert|volumetric.frag| grass.mesh|model.frag|#define FOLIAGE;#define BLADE;#define MESH; grass.vert|model.frag|#define FOLIAGE;#define BLADE; grass.vert|model.frag|#define FOLIAGE;#define BLADE;#define TILES; @@ -26,12 +28,15 @@ model.vert|bake.frag| model.vert|bake.frag|#define CARD; model.vert|bake.frag|#define FLOWER; model.vert|depth.frag|#define FOLIAGE;#define WIND;#define ALPHA_TEST; +model.vert|depth.frag|#define FOLIAGE;#define WIND;#define ALPHA_TEST;#define NEAR_FADE; model.vert|model.frag| model.vert|model.frag|#define CARD;#define CHEAP; model.vert|model.frag|#define FOLIAGE;#define WIND;#define ALPHA_TEST; +model.vert|model.frag|#define FOLIAGE;#define WIND;#define ALPHA_TEST;#define NEAR_FADE; model.vert|model.frag|#define FOLIAGE;#define WIND;#define BLADE; model.vert|model.frag|#define FOLIAGE;#define WIND;#define CARD; model.vert|model.frag|#define FOLIAGE;#define WIND;#define EQ_PASS; +model.vert|model.frag|#define FOLIAGE;#define WIND;#define EQ_PASS;#define NEAR_FADE; model.vert|model.frag|#define FOLIAGE;#define WIND;#define FLOWER; model.vert|model.frag|#define SHADOW_PASS;#define WIND;#define CARD; model.vert|model.frag|#define WIND; @@ -54,5 +59,3 @@ terrain.vert|terrain.frag|#define SUN_INLINE;#define FAR_ONLY; terrain.vert|terrain.frag|#define SUN_INLINE;#define NEAR_ONLY; terrain.vert|tersun.frag| water.vert|water.frag| -fullscreen.vert|volumetric.frag| -fullscreen.vert|dof.frag| diff --git a/runtime/native/cocoa.ll b/runtime/native/cocoa.ll index 81f4e225..d6800e0c 100644 --- a/runtime/native/cocoa.ll +++ b/runtime/native/cocoa.ll @@ -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 +} diff --git a/runtime/native/gl.ll b/runtime/native/gl.ll index 5b7b6f74..235a254a 100644 --- a/runtime/native/gl.ll +++ b/runtime/native/gl.ll @@ -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 diff --git a/runtime/native/gl_win_nowin.ll b/runtime/native/gl_win_nowin.ll index 539ed6f6..52025b88 100644 --- a/runtime/native/gl_win_nowin.ll +++ b/runtime/native/gl_win_nowin.ll @@ -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: diff --git a/runtime/native/vk_mac.ll b/runtime/native/vk_mac.ll index 51c976db..e34cd65b 100644 --- a/runtime/native/vk_mac.ll +++ b/runtime/native/vk_mac.ll @@ -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 diff --git a/runtime/native/win32.ll b/runtime/native/win32.ll index 81cd4bd7..c7fc24d3 100644 --- a/runtime/native/win32.ll +++ b/runtime/native/win32.ll @@ -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) { diff --git a/selfhost/backend/emit_intrin.ludic b/selfhost/backend/emit_intrin.ludic index a7907d6a..ef33a771 100644 --- a/selfhost/backend/emit_intrin.ludic +++ b/selfhost/backend/emit_intrin.ludic @@ -20,6 +20,12 @@ function is_intrinsic(name: pointer) -> bool { function emit_bind(rest: pointer) -> pointer { let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r } function arg_code(e: Node, i: int) -> pointer { let v = emit_expr(e.kids[i]); return v.code } +# a buffer handed to the platform layer: a slice's elements, never its header (as an extern's are) +function arg_buf(e: Node, i: int) -> pointer { + let v = emit_expr(e.kids[i]) + if is_slice_ty(v.ty) { return emit_bind(`load ptr, ptr {slice_field(v.code, 0)}`) } + return v.code +} function emit_intrinsic(name: pointer, e: Node) -> Val { g_intrin_ok = true diff --git a/selfhost/backend/emit_intrin2.ludic b/selfhost/backend/emit_intrin2.ludic index 594f9364..61514fa0 100644 --- a/selfhost/backend/emit_intrin2.ludic +++ b/selfhost/backend/emit_intrin2.ludic @@ -70,7 +70,7 @@ function emit_intrinsic2(name: pointer, e: Node) -> Val { return val("0", "void") } if (name == "win_present") { - let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2) + let a = arg_buf(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2) emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n") return val("0", "void") } @@ -78,19 +78,19 @@ function emit_intrinsic2(name: pointer, e: Node) -> Val { # #50 — fill a caller buffer with the platform device state (windowed only, # DCE'd headless). win_held: an 8-word held-key bitset; win_mouse: [x, y, # button-mask, wheel-delta]. - if (name == "win_held") { let a = arg_code(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") } - if (name == "win_mouse") { let a = arg_code(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") } + if (name == "win_held") { let a = arg_buf(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") } + if (name == "win_mouse") { let a = arg_buf(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") } # what the active keyboard layout types on a held-set key position (windowed only, # DCE'd headless): a code point, 0 for none. if (name == "win_key_char") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @win_key_char(i32 {a})`), "int") } # what the player TYPED since the last call - UTF-16 code units into the caller's buffer, one per # word, returning how many. The layout, the modifiers and any dead key are already applied, which # is why a name field must read this rather than build text out of key codes. - if (name == "win_text") { let a = arg_code(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call i32 @win_text(ptr {a}, i32 {b})`), "int") } + if (name == "win_text") { let a = arg_buf(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call i32 @win_text(ptr {a}, i32 {b})`), "int") } # #51 — win_pad: 6 words/pad [connected, mask, lx, ly, rx, ry]; win_touch: 3 # words/point [active, x, y]. Both windowed-only, DCE'd headless. - if (name == "win_pad") { let a = arg_code(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") } - if (name == "win_touch") { let a = arg_code(e, 0); emit(" call void @win_touch(ptr "); emit(a); emit(")\n"); return val("0", "void") } + if (name == "win_pad") { let a = arg_buf(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") } + if (name == "win_touch") { let a = arg_buf(e, 0); emit(" call void @win_touch(ptr "); emit(a); emit(")\n"); return val("0", "void") } # #89 — set the OS cursor capture mode (windowed only; DCE'd headless). if (name == "win_cursor_mode") { let a = arg_code(e, 0); emit(" call void @win_cursor_mode(i32 "); emit(a); emit(")\n"); return val("0", "void") } # #22 audio backend — AVAudioPlayer via audio.ll; windowed-only, DCE'd headless. diff --git a/selfhost/main.ludic b/selfhost/main.ludic index d4222bad..566b4982 100644 --- a/selfhost/main.ludic +++ b/selfhost/main.ludic @@ -358,6 +358,8 @@ entry { var vkl = join_path(home, "runtime/native/vk_mac.ll") if g_target_win { vkl = join_path(home, "runtime/native/vk_win.ll") } cmd = `{cmd} {vkt} {vkl}` + # the CAMetalLayer a macOS window presents through, found by name in cocoa.ll + if not g_target_win { cmd = `{cmd} -Wl,-needed_framework,QuartzCore` } } # A shipped Windows game is a GUI-subsystem program: started from Explorer, a # console-subsystem one opens a console window behind the game. mainCRTStartup keeps diff --git a/tools/ludic-cli/build.ludic b/tools/ludic-cli/build.ludic index 6a7f7de6..39c62872 100644 --- a/tools/ludic-cli/build.ludic +++ b/tools/ludic-cli/build.ludic @@ -102,7 +102,8 @@ function gl_link_flags(ll: pointer) -> pointer { function vk_link_flags(ll: pointer) -> pointer { if not shq(`grep -q "@lvk_" {ll}`) { return "" } let home = ludic_home() - return ` {home}runtime/native/vk_thunks.ll {home}runtime/native/vk_mac.ll` + # QuartzCore: the CAMetalLayer a window presents through (cocoa.ll finds the class by name) + return ` {home}runtime/native/vk_thunks.ll {home}runtime/native/vk_mac.ll -Wl,-needed_framework,QuartzCore` } # A program that uses Http.* calls the @hs_* transport; link http.ll and Foundation only