diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f7bbf0..2406cb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- MetalCompilerPlugin: .metal files now compile automatically at build time — + checked-in metallib and manual tools/build_shaders.sh step are gone + - StyleReel + StyleReelVertical (9:16) — 12-aesthetic showcase scenes with card-zoom transitions + tools/make_reel_audio.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4fb8b1a..0528426 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,17 +31,11 @@ open out/test.mp4 ## Adding a shader -1. Add a `[[ stitchable ]]` function to `Sources/SwiftRender/Shaders/Cookbook.metal` (or create a new `.metal` file in `Shaders/`). -2. Rebuild the metallib: - ```bash - cd Sources/SwiftRender/Shaders - xcrun -sdk macosx metal -c *.metal -o /tmp/all.air - xcrun -sdk macosx metallib /tmp/*.air -o ../Resources/default.metallib - ``` +1. Add a `[[ stitchable ]]` function to a `.metal` file in `Sources/SwiftRender/Shaders/`. +2. `swift build` — the MetalCompilerPlugin compiles all shaders into the + metallib automatically (requires full Xcode for the metal toolchain). 3. Use it in a scene via `ShaderLibrary.bundle(.module).yourShader(...)`. -> If you can automate step 2 via a SwiftPM build tool plugin, please open a PR — that's high on the wishlist. - ## Style - Keep scenes pure functions of `t`. No `@State`, no `Timer`, no `withAnimation(.repeatForever)`. diff --git a/Package.swift b/Package.swift index fa6c344..53f9e2e 100644 --- a/Package.swift +++ b/Package.swift @@ -12,15 +12,20 @@ let package = Package( .target( name: "SwiftRender", dependencies: [], + exclude: ["Shaders"], resources: [ .process("Resources"), - .process("Shaders"), - ] + ], + plugins: ["MetalCompilerPlugin"] ), .executableTarget( name: "SwiftRenderCLI", dependencies: ["SwiftRender"] ), + .plugin( + name: "MetalCompilerPlugin", + capability: .buildTool() + ), .testTarget( name: "SwiftRenderTests", dependencies: ["SwiftRender"] diff --git a/Plugins/MetalCompilerPlugin/plugin.swift b/Plugins/MetalCompilerPlugin/plugin.swift new file mode 100644 index 0000000..7a66f15 --- /dev/null +++ b/Plugins/MetalCompilerPlugin/plugin.swift @@ -0,0 +1,31 @@ +import Foundation +import PackagePlugin + +/// Compiles Shaders/*.metal into default.metallib at build time, replacing +/// the checked-in metallib + tools/build_shaders.sh manual step. +@main +struct MetalCompilerPlugin: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + guard let target = target as? SourceModuleTarget else { return [] } + let shaderDir = target.directory.appending("Shaders") + let fm = FileManager.default + guard let entries = try? fm.contentsOfDirectory(atPath: shaderDir.string) else { return [] } + let metalFiles = entries.filter { $0.hasSuffix(".metal") }.sorted() + .map { shaderDir.appending($0) } + guard !metalFiles.isEmpty else { return [] } + + let out = context.pluginWorkDirectory.appending("default.metallib") + // the plugin sandbox only permits writes inside pluginWorkDirectory — + // metal's clang module cache must live there too or cold builds fail + let moduleCache = context.pluginWorkDirectory.appending("ModuleCache") + return [.buildCommand( + displayName: "Compiling \(metalFiles.count) Metal shaders → default.metallib", + executable: Path("/usr/bin/xcrun"), + arguments: ["-sdk", "macosx", "metal", + "-fmodules-cache-path=\(moduleCache.string)"] + + metalFiles.map(\.string) + ["-o", out.string], + inputFiles: metalFiles, + outputFiles: [out] + )] + } +} diff --git a/README.md b/README.md index 4bcdfce..2e7b3e1 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ Pipe in JSON per record and render a thousand personalized variants — the AI/d ## Real Metal shaders -Drop a `.metal` file in `Sources/SwiftRender/Shaders/`, run `tools/build_shaders.sh`, call it on any view: +Drop a `.metal` file in `Sources/SwiftRender/Shaders/` and `swift build` — shaders compile automatically (SwiftPM build plugin). Call them on any view: ```swift Rectangle().fill(.black).colorEffect( diff --git a/Sources/SwiftRender/Resources/default.metallib b/Sources/SwiftRender/Resources/default.metallib deleted file mode 100644 index 8dc2baa..0000000 Binary files a/Sources/SwiftRender/Resources/default.metallib and /dev/null differ