From 9425bdc4e51a28166a020423dcae312d003f3ce1 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 01:16:26 +0300 Subject: [PATCH] feat(render3d): 22.10 - ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver native/build.sh builds MoltenVK v1.4.2 from its pinned, checksummed tag (its dependencies at the commits its ExternalRevisions pins), thinned to arm64, id @rpath/libMoltenVK.dylib, signed ad hoc; its licence goes in native/LICENSE-MoltenVK. Every render3d program links it through its rpath - the package's lib/ while developing, Contents/Frameworks in a bundle, where ludic bundle puts and signs it - and vk_mac.ll also looks for @rpath/libMoltenVK.dylib (after an SDK loader, so the validation layer still stacks in development). The suite's SDK stand-in (vk_env) is gone: the render checks draw on the MoltenVK the package carries. gpu_is_gl() removed; nothing calls it. ludic-dev test 306/306 with no Vulkan SDK in the environment. Co-Authored-By: Claude Opus 5.5 --- changes/render3d-moltenvk.md | 10 + packages/ludic.render3d/gpu.ludic | 2 - .../lib/macos-arm64/libMoltenVK.dylib | 3 + .../ludic.render3d/native/LICENSE-MoltenVK | 202 ++++++++++++++++++ packages/ludic.render3d/native/build.sh | 25 +++ packages/ludic.render3d/package.ludic | 7 +- packages/ludic.render3d/post.ludic | 2 +- runtime/native/vk_mac.ll | 10 +- tools/ludic-cli/test.ludic | 16 +- 9 files changed, 254 insertions(+), 23 deletions(-) create mode 100644 changes/render3d-moltenvk.md create mode 100755 packages/ludic.render3d/lib/macos-arm64/libMoltenVK.dylib create mode 100644 packages/ludic.render3d/native/LICENSE-MoltenVK create mode 100755 packages/ludic.render3d/native/build.sh diff --git a/changes/render3d-moltenvk.md b/changes/render3d-moltenvk.md new file mode 100644 index 00000000..25fba9eb --- /dev/null +++ b/changes/render3d-moltenvk.md @@ -0,0 +1,10 @@ +bump: minor +type: feature +**ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver without the SDK.** +`native/build.sh` builds MoltenVK from a pinned tag (v1.4.2, its SHA-256 checked; its own +dependencies at the commits its ExternalRevisions pins), thinned to arm64 and signed, into +`lib/macos-arm64/libMoltenVK.dylib`, with its licence in `native/LICENSE-MoltenVK`. Every program +that imports render3d links it through its rpath - the package's lib/ while developing, +`Contents/Frameworks` in a bundle, where `ludic bundle` puts and signs it - and the loader +(`vk_mac.ll`) now also looks for `@rpath/libMoltenVK.dylib`. An SDK loader found first still wins, +so the validation layer can be stacked in development. `gpu_is_gl()` is gone. diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index ab0f2cf8..9def4f8e 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -46,8 +46,6 @@ function gpu_select(render3d_st: mut Render3dState) -> int { return render3d_st.gpu_kind } function gpu_backend(render3d_st: Render3dState) -> string { return "vulkan" } -# always false now; kept until the game stops asking (maroon-lake src/app/boot_load.ludic) -function gpu_is_gl(render3d_st: Render3dState) -> bool { return false } # ---- render state ---------------------------------------------------------------- # -1 = not known yet: the first set always reaches the driver, so the cache never diff --git a/packages/ludic.render3d/lib/macos-arm64/libMoltenVK.dylib b/packages/ludic.render3d/lib/macos-arm64/libMoltenVK.dylib new file mode 100755 index 00000000..5bef2a02 --- /dev/null +++ b/packages/ludic.render3d/lib/macos-arm64/libMoltenVK.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:381deb6b53246cde0dc005f8ccd5bde2558f9d2496ed945dded6ec6d7ea5a879 +size 5305840 diff --git a/packages/ludic.render3d/native/LICENSE-MoltenVK b/packages/ludic.render3d/native/LICENSE-MoltenVK new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/packages/ludic.render3d/native/LICENSE-MoltenVK @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/ludic.render3d/native/build.sh b/packages/ludic.render3d/native/build.sh new file mode 100755 index 00000000..5e27a02a --- /dev/null +++ b/packages/ludic.render3d/native/build.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# builds lib/macos-arm64/libMoltenVK.dylib: Vulkan over Metal, which render3d draws with on a Mac. +# MoltenVK at a pinned tag, its SHA-256 checked; its dependencies (SPIRV-Cross, SPIRV-Tools, +# Vulkan-Headers, ...) come at the commits its own ExternalRevisions pins. Needs Xcode (not only the +# command line tools); about fifteen minutes. Windows needs nothing: its drivers install the loader. +set -eu +PKG="$(cd "$(dirname "$0")/.." && pwd)" +. "$PKG/../../tools/native/lib.sh" +case "$(native_target)" in macos-*) ;; *) echo "ludic.render3d: nothing to build for $(native_target)"; exit 0 ;; esac +MVK_TAG=v1.4.2 +MVK_SHA=6864db532f1dbbdb621a8d0ec13f24edae318fd9269dd3dd0cdff791334bb1cb +SRC="$PKG/build/src/moltenvk-$MVK_TAG" +native_fetch "$SRC" "https://github.com/KhronosGroup/MoltenVK/archive/refs/tags/$MVK_TAG.tar.gz" "$MVK_SHA" +XCODE="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}" +( cd "$SRC" && DEVELOPER_DIR="$XCODE" ./fetchDependencies --macos && DEVELOPER_DIR="$XCODE" make macos ) +OUT="$PKG/lib/$(native_target)" +mkdir -p "$OUT" +# the release is Apple silicon only: the arm64 slice, found by the executable's rpath +lipo "$SRC/Package/Release/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" -thin arm64 -output "$OUT/libMoltenVK.dylib" +install_name_tool -id @rpath/libMoltenVK.dylib "$OUT/libMoltenVK.dylib" 2>/dev/null +# the id change breaks the signature, and Apple silicon loads no unsigned code: signed ad hoc here, +# with the Developer ID by ludic bundle / vpk for a release +codesign -f -s - "$OUT/libMoltenVK.dylib" +cp "$SRC/LICENSE" "$PKG/native/LICENSE-MoltenVK" +echo "$OUT/libMoltenVK.dylib" diff --git a/packages/ludic.render3d/package.ludic b/packages/ludic.render3d/package.ludic index 0785df3e..b8604c3d 100644 --- a/packages/ludic.render3d/package.ludic +++ b/packages/ludic.render3d/package.ludic @@ -1,7 +1,8 @@ -# ludic.render3d — a physically based 3D renderer over Gl.* (OpenGL 4.1 core): -# HDRI sky + image-based lighting, a GPU-generated terrain with scanned PBR -# materials, cascaded shadow maps, an HDR pipeline with bloom and ACES. +# ludic.render3d — a physically based 3D renderer over Vulkan (MoltenVK on a Mac, carried here, +# built from a pinned tag by native/build.sh): HDRI sky + image-based lighting, a GPU-generated +# terrain with scanned PBR materials, cascaded shadow maps, an HDR pipeline with bloom and ACES. package "ludic.render3d" version "0.1.0" kind source provides "R3d" +native "macos-arm64" "lib/macos-arm64/libMoltenVK.dylib" diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index db96777e..20ca2711 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -39,7 +39,7 @@ function post_free(render3d_st: mut Render3dState) -> void { # Multisampled scene: 1 (temporal AA alone), 2 or 4, remade at once. Vulkan draws it into # multisampled renderbuffers and resolves them in a pass; a device that cannot take the count asked # for gets the most it can (gpu_msaa_max). -function post_msaa_live(render3d_st: Render3dState) -> bool { return gpu_is_gl(render3d_st) or gpu_msaa_max(render3d_st) > 1 } +function post_msaa_live(render3d_st: Render3dState) -> bool { return gpu_msaa_max(render3d_st) > 1 } function post_set_msaa(render3d_st: mut Render3dState, n: int) -> void { var want = n if want < 1 { want = 1 } diff --git a/runtime/native/vk_mac.ll b/runtime/native/vk_mac.ll index 9cd51b9c..63bad56b 100644 --- a/runtime/native/vk_mac.ll +++ b/runtime/native/vk_mac.ll @@ -27,6 +27,7 @@ declare i32 @lvk_bind() @.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_mr = private unnamed_addr constant [25 x i8] c"@rpath/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 @@ -40,8 +41,9 @@ entry: ; 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] +; a bundle ships in Contents/Frameworks - no loader, no ICD json - or, found through the +; executable's rpath, what ludic.render3d carries (lib/macos-arm64) for every other build. +@lvk_paths = internal constant [8 x ptr] [ptr @.lvk_c0, ptr @.lvk_c1, ptr @.lvk_c2, ptr @.lvk_c3, ptr @.lvk_m0, ptr @.lvk_mr, ptr @.lvk_m1, ptr @.lvk_m2] define i32 @lvk_open() { entry: @@ -50,14 +52,14 @@ entry: 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 + %slot = getelementptr [8 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 + %more = icmp slt i32 %i1, 8 br i1 %more, label %loop, label %sdk got: store ptr %h, ptr @lvk_lib diff --git a/tools/ludic-cli/test.ludic b/tools/ludic-cli/test.ludic index 85656353..75ba6609 100644 --- a/tools/ludic-cli/test.ludic +++ b/tools/ludic-cli/test.ludic @@ -455,7 +455,7 @@ function lab_plate_case() -> void { # from the checkout's root, where the renderer's shaders and the plate are found beside it let work = `{tmp_dir()}/labrun` shell(`rm -rf {work} build/lab/probe && mkdir -p {work}`) - if not shq(`yes '' | head -300 | {vk_env()}/usr/bin/time -l {bin} > {work}/run.out 2> {work}/time.out`) { bad2(lbl, capture_line(`tail -1 {work}/run.out`)); return } + if not shq(`yes '' | head -300 | /usr/bin/time -l {bin} > {work}/run.out 2> {work}/time.out`) { bad2(lbl, capture_line(`tail -1 {work}/run.out`)); return } var n = 0 let names = ["front", "side", "above"] for i in 0 .. 3 { @@ -608,22 +608,12 @@ function headless_case(path: pointer, exp: pointer, label: pointer) -> void { let got = capture_line(`{out} < /dev/null`) if got == exp { ok(label) } else { bad2(label, `got [{got}] want [{exp}]`) } } -# render3d draws with Vulkan only: on a Mac with no loader beside the binary, a run finds MoltenVK -# through the SDK - VULKAN_SDK as set, else the newest ~/VulkanSDK//macOS -function vk_env() -> string { - let set = Os.env("VULKAN_SDK") - if set != null and set != "" { return "" } - let found = capture_line("ls -d $HOME/VulkanSDK/*/macOS 2>/dev/null | tail -1") - if len(found) == 0 { return "" } - # the SDK's loader finds MoltenVK only through its driver manifest - return `VULKAN_SDK={found} VK_ICD_FILENAMES={found}/share/vulkan/icd.d/MoltenVK_icd.json ` -} # a program built headless whose verdict is one of its lines (the renderer logs before it) function headless_line_case(path: pointer, exp: pointer, label: pointer) -> void { let out = `{tmp_dir()}/h_{flat(path)}` if not shq(`bin/ludicc --headless examples/{path}.ludic -o {out} > {out}.log 2>&1`) { bad2(label, capture_line(`grep -i error {out}.log | head -1`)); return } - let got = capture_line(`{vk_env()}{out} < /dev/null 2>&1 | grep -c '^{exp}$'`) - if got == "1" { ok(label) } else { bad2(label, capture_line(`{vk_env()}{out} < /dev/null 2>&1 | grep -i -E 'fail|cannot' | head -1`)) } + let got = capture_line(`{out} < /dev/null 2>&1 | grep -c '^{exp}$'`) + if got == "1" { ok(label) } else { bad2(label, capture_line(`{out} < /dev/null 2>&1 | grep -i -E 'fail|cannot' | head -1`)) } } # ludic migrate state and components: the header names what every member needs, a field read in a # member is not edited, a module named like a package keeps a state of its own, and the program