Files
luma/Sources/LumaCore/ShaderTranslator.swift
Ole André Vadla Ravnås 02ca1ff8a5 Make the shader toolchain, don't find one
glslang and SPIRV-Cross build under neither SwiftPM nor Xcode, so
CI makes them into an xcframework and publishes it against a tag.
The manifest names that artifact; a build downloads it and needs
nothing local.
2026-08-13 12:46:09 +02:00

62 lines
2.3 KiB
Swift

#if canImport(Metal)
import CShaderTranslate
import Foundation
/// Turns GLSL written at runtime into Metal Shading Language, so an effect
/// typed into a snippet reaches a Metal host without a build step. The GTK
/// host needs none of this: OpenGL takes the GLSL as it stands.
public enum ShaderTranslator {
/// The preamble every authored effect is compiled against, naming the
/// uniforms it may read. It matches what `LumaShaderCompiler` prepends at
/// build time, so an effect reads the same whether it was authored here or
/// shipped in `Shaders/`.
/// What a screen-filling effect is compiled against: the vertex stage is
/// the host's, handing down only where on screen the pixel sits.
public static var preamble: String {
"""
\(ShaderVocabulary.header)
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 frag_color;
\(ShaderVocabulary.uniformBlock)
"""
}
/// Answers the Metal source for `body`, which carries only its own helpers
/// and `main()`.
public static func metalSource(for body: String, entryPoint: String) throws -> String {
try metalSource(forComplete: preamble + body, stage: .fragment, entryPoint: entryPoint)
}
public enum Stage: Int32 {
case vertex = 0
case fragment = 1
}
/// Translates a whole shader, preamble and all, for callers that build
/// their own -- a canvas whose attributes the author declared.
public static func metalSource(forComplete glsl: String, stage: Stage, entryPoint: String) throws -> String {
var message: UnsafeMutablePointer<CChar>?
let translated = luma_shader_translate_to_msl(glsl, stage.rawValue, entryPoint, &message)
guard let translated else {
let detail = message.map { String(cString: $0) } ?? "no detail"
message.map { free($0) }
throw ShaderTranslationError(message: detail)
}
defer { free(translated) }
return String(cString: translated)
}
}
/// Carries what the shader compiler said, so a mistyped effect is reported
/// where it was written rather than drawing nothing.
public struct ShaderTranslationError: Error, CustomStringConvertible {
public let message: String
public var description: String { message }
}
#endif