import std; import Crafter.Build; namespace fs = std::filesystem; using namespace Crafter; extern "C" Configuration CrafterBuildProject(std::span args) { std::vector depArgs(args.begin(), args.end()); // --local resolves Crafter.* deps from sibling working trees instead of // fetching them from forgejo. Use during cross-repo development so edits // in ../Crafter.Asset are picked up without commit-and-pull. Add to // depArgs too so transitive deps inherit the same mode. bool useLocal = false; for (std::string_view a : args) { if (a == "--local") { useLocal = true; break; } } if (useLocal && std::find(depArgs.begin(), depArgs.end(), std::string("--local")) == depArgs.end()) { depArgs.push_back("--local"); } auto resolveDep = [&](std::string_view name, std::string_view gitUrl) -> Configuration* { if (useLocal) { return LocalProject({ .projectFile = fs::path("../") / name / "project.cpp", .args = depArgs, }); } return GitProject({ .source = { .url = std::string(gitUrl) }, .args = depArgs, }); }; // Sniff the requested target from args before any deps resolve — the // Crafter.Asset dependency is heavy and not wasm-ready (uses `throw` // under -fno-exceptions, references `_Float16`). The DOM build stubs // the renderer entirely so the dep doesn't apply anyway. bool isWasm = false; for (std::string_view a : args) { if (a.starts_with("--target=") && a.find("wasm") != std::string_view::npos) { isWasm = true; break; } } Configuration* event = resolveDep("Crafter.Event", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git"); Configuration* math = resolveDep("Crafter.Math", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git"); Configuration* asset = isWasm ? nullptr : resolveDep("Crafter.Asset", "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git"); Configuration cfg; cfg.path = "./"; cfg.name = "Crafter.Graphics"; cfg.outputName = "Crafter.Graphics"; cfg.type = ConfigurationType::LibraryStatic; auto opts = ApplyStandardArgs(cfg, args); if (asset) { cfg.dependencies = { event, math, asset }; } else { cfg.dependencies = { event, math }; } // Window backend follows the target triple. V1 had separate lib-wayland / // lib-win32 configurations; V2 picks the right one automatically based on // where the build is going. Cross-compile (`--target=...`) flips the // backend along with everything else. The DOM backend is reached by any // wasm32-* target and produces a Vulkan-free build whose Window is wired // to a custom JS env (see additional/dom-env.js). bool dom = cfg.target.find("wasm") != std::string::npos; bool windows = !dom && (cfg.target.find("windows") != std::string::npos || cfg.target.find("mingw") != std::string::npos); if (dom) { cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_DOM", ""}); // No native window libs, no Vulkan loader, no Wayland/X11. The JS // bridge satisfies every dynamic symbol via wasm imports. Crafter.Build // strips -march/-mtune from the clang command line for any wasm32-* // triple, so cfg.march/mtune can stay at their defaults — keeping them // matches the VariantId of dependency PCMs. } else if (windows) { cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_WIN32", ""}); cfg.linkFlags.push_back("-lkernel32"); cfg.linkFlags.push_back("-luser32"); cfg.linkFlags.push_back("-lgdi32"); // Windows.Gaming.Input (WGI) needs the WinRT activation runtime // and combase for HSTRING / RoGetActivationFactory. cfg.linkFlags.push_back("-lruntimeobject"); cfg.linkFlags.push_back("-lcombase"); } else { cfg.defines.push_back({"CRAFTER_GRAPHICS_WINDOW_WAYLAND", ""}); cfg.linkFlags.push_back("-lwayland-client"); cfg.linkFlags.push_back("-lxkbcommon"); // Gamepad: libudev for hot-plug + device enumeration; libevdev // for event parsing + axis calibration. libevdev ships its headers // under a versioned dir (libevdev-1.0/) so the -I is mandatory. cfg.linkFlags.push_back("-ludev"); cfg.linkFlags.push_back("-levdev"); cfg.compileFlags.push_back("-I/usr/include/libevdev-1.0"); cfg.cFiles.push_back("lib/xdg-shell-protocol"); cfg.cFiles.push_back("lib/wayland-xdg-decoration-unstable-v1-client-protocol"); cfg.cFiles.push_back("lib/fractional-scale-v1"); cfg.cFiles.push_back("lib/viewporter"); } // Vulkan is the only renderer on native targets. Software fallback is // provided externally via the Vulkan loader (e.g. llvmpipe / lavapipe) — // no separate code path. The DOM backend doesn't render in V1 (the // UIRenderer and every Vulkan-typed module are excluded below); the // WebGPU follow-up will gain its own headers/loader rather than reuse // the Vulkan ones. if (!dom) { ExternalDependency& vkHeaders = cfg.externalDependencies.emplace_back(); vkHeaders.name = "Vulkan-Headers"; vkHeaders.source.url = "https://github.com/KhronosGroup/Vulkan-Headers.git"; vkHeaders.builder = ExternalBuilder::None; vkHeaders.includeDirs = { "include" }; ExternalDependency& vkUtility = cfg.externalDependencies.emplace_back(); vkUtility.name = "Vulkan-Utility-Libraries"; vkUtility.source.url = "https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git"; vkUtility.builder = ExternalBuilder::None; vkUtility.includeDirs = { "include" }; cfg.linkFlags.push_back(windows ? "-lvulkan-1" : "-lvulkan"); } if (opts.Has("--timing")) cfg.defines.push_back({"CRAFTER_TIMING", ""}); // One master interface list. Every partition exists on every target // — Crafter.Build's dependency scanner doesn't respect `#ifdef` on // `import :X` statements, so the partition file must be present even // when its body is gated out. Vulkan-typed partitions stub to empty // modules under CRAFTER_GRAPHICS_WINDOW_DOM; the Dom/DomEvents/Router // partitions stub to empty modules in the opposite direction. std::array ifaces = { "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-Animation", "interfaces/Crafter.Graphics-Clipboard", "interfaces/Crafter.Graphics-ComputeShader", "interfaces/Crafter.Graphics-Decompress", "interfaces/Crafter.Graphics-DescriptorHeapVulkan", "interfaces/Crafter.Graphics-Device", "interfaces/Crafter.Graphics-Dom", "interfaces/Crafter.Graphics-DomEvents", "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-FontAtlas", "interfaces/Crafter.Graphics-ForwardDeclarations", "interfaces/Crafter.Graphics-Gamepad", "interfaces/Crafter.Graphics-ImageVulkan", "interfaces/Crafter.Graphics-Input", "interfaces/Crafter.Graphics-InputField", "interfaces/Crafter.Graphics-Keys", "interfaces/Crafter.Graphics-Mesh", "interfaces/Crafter.Graphics-PipelineRTVulkan", "interfaces/Crafter.Graphics-RenderingElement3D", "interfaces/Crafter.Graphics-RenderPass", "interfaces/Crafter.Graphics-Router", "interfaces/Crafter.Graphics-RTPass", "interfaces/Crafter.Graphics-SamplerVulkan", "interfaces/Crafter.Graphics-ShaderBindingTableVulkan", "interfaces/Crafter.Graphics-ShaderVulkan", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-UI", "interfaces/Crafter.Graphics-UIComponents", "interfaces/Crafter.Graphics-VulkanBuffer", "interfaces/Crafter.Graphics-VulkanTransition", "interfaces/Crafter.Graphics-Window", }; if (dom) { // DOM impl set: only the files whose bodies do meaningful work // under CRAFTER_GRAPHICS_WINDOW_DOM. Vulkan-only impls (Mesh, // ComputeShader, Font, etc.) stay out — their interface stubs // mean no link-side references; including the impl would just // be dead code with no Vulkan headers to compile against. std::array domImpls = { "implementations/Crafter.Graphics-Clipboard", "implementations/Crafter.Graphics-Dom", "implementations/Crafter.Graphics-Gamepad", "implementations/Crafter.Graphics-Input", "implementations/Crafter.Graphics-Router", "implementations/Crafter.Graphics-Window", }; cfg.GetInterfacesAndImplementations(ifaces, domImpls); // JS glue shipped alongside the .wasm so the loader has the // env-import surface the Window/Dom bindings expect. cfg.files.emplace_back(fs::path("additional/dom-env.js")); } else { std::array impls = { "implementations/Crafter.Graphics-Clipboard", "implementations/Crafter.Graphics-ComputeShader", "implementations/Crafter.Graphics-Device", "implementations/Crafter.Graphics-Font", "implementations/Crafter.Graphics-FontAtlas", "implementations/Crafter.Graphics-Gamepad", "implementations/Crafter.Graphics-Input", "implementations/Crafter.Graphics-InputField", "implementations/Crafter.Graphics-Mesh", "implementations/Crafter.Graphics-RenderingElement3D", "implementations/Crafter.Graphics-UI", "implementations/Crafter.Graphics-UIComponents", "implementations/Crafter.Graphics-Window", }; cfg.GetInterfacesAndImplementations(ifaces, impls); cfg.shaders.emplace_back(fs::path("shaders/ui-quads.comp.glsl"), std::string("main"), ShaderType::Compute); cfg.shaders.emplace_back(fs::path("shaders/ui-circles.comp.glsl"), std::string("main"), ShaderType::Compute); cfg.shaders.emplace_back(fs::path("shaders/ui-images.comp.glsl"), std::string("main"), ShaderType::Compute); cfg.shaders.emplace_back(fs::path("shaders/ui-text.comp.glsl"), std::string("main"), ShaderType::Compute); cfg.buildFiles.emplace_back(fs::path("shaders/ui-shared.glsl")); } return cfg; }