Msl.h file
Metal Shading Language (MSL, macOS) source-to-source emitter for ShaderCompiler.
The tool lowers each ".shader" into a MODERN-GLSL stage source (in/out, texture(), "out vec4 COLOR;", std140 UBO blocks, gl_VertexID). This emitter re-emits that stage from the shared typed AST (GlslTypedAst.h, the same front-end the HLSL and Vulkan emitters use) as one MSL translation unit per stage, which the Metal backend hands to MTLDevice::newLibrary() at load time - there is no offline MSL compiler outside Xcode, so unlike DXBC and SPIR-V the artifact is SOURCE (like the PS Vita's Cg). This is TOOL-ONLY: it adds no RHI backend and touches no engine code.
The key GLSL -> MSL rewrites:
- Types: vec2/3/4 -> float2/3/4, mat3/4 -> float3x3/float4x4, ivecN -> intN, bvecN -> boolN; precision and layout qualifiers are dropped. Matrix algebra keeps the GLSL
*operator (MSL's*on matrices and vectors has the same column-vector linear-algebra meaning, so no mul() rewrite is needed). - Uniforms: MSL has no globals other than
constantdata, so every uniform reaches the shader as an entry-point argument. The loose (default-block) uniforms gather into ONE_Globalsstruct bound at[[buffer(0)]](present only when the merged reflection has any loose uniform), and everylayout(std140) uniform Block { ... }becomes aconstant Block&argument at[[buffer(uboBase + i)]](uboBase = 1 when_Globalsexists, else 0; i = the block's index in the merged reflection). The vertex stream is bound at[[buffer(30)]](MslVertexBufferIndex), well away from the uniform indices. Block members are accessed as_b<Block>.member, so both the bare-member and theinstance.memberspellings of the GLSL resolve to the same thing. - std140 layout: MSL lays a
constantstruct out with C rules, which agree with std140 for float/int, vec2, vec4, mat3 and mat4 and for structs made of those - the whole shipped set - but not for vec3 followed by a scalar, mat2, or arrays of scalars/vec2. The emitter therefore checks every member against its reflected std140 offset, inserts explicit padding where MSL would place a member EARLIER, spells a vec3packed_float3where a later member must land inside its tail padding, and declines a block it cannot make agree (an array whose natural stride differs from the std140 one, a mat2, a bool). The backend copies the engine's std140 bytes verbatim into the bound buffers, so this agreement is what makes every uniform read the right value. - Textures:
uniform sampler2D uTex->texture2d<float> uTex [[texture(j)]], sampler uTex_smplr [[sampler(j)]](j = the sampler's index in the merged reflection), andtexture(uTex, uv)->uTex.sample(uTex_smplr, uv)(textureLod->.sample(..., level(l)),texelFetch->.read()). - I/O: vertex attributes form a
[[stage_in]]struct with[[attribute(location)]]members;gl_VertexID/gl_InstanceIDare[[vertex_id]]/[[instance_id]]arguments; VSoutvaryings +gl_Position([[position]]) form the VS return struct; FSinvaryings +gl_FragCoord([[position]]) form the FS[[stage_in]]struct. Varyings carry[[user(locnN)]]in DECLARATION ORDER so the two stages link by index rather than by name, and integer /flatvaryings get[[flat]](Metal requires it). A single FSoutis returned asfloat4 [[color(0)]]; MULTIPLE outputs render to[[color(0..N)]]in DECLARATION ORDER through an emitted FsOut struct - the same order the HLSL and SPIR-V emissions assign (see Hlsl.h / Vulkan.h). - Helper functions: MSL has no mutable program-scope variables and no implicit access to entry-point arguments, so every helper that (transitively) reads a uniform, block, sampler, attribute, varying or fragment output receives it as an extra trailing parameter (
constant _Globals&,constant Block&,texture2d<float>+sampler,thread VsOut&/thread FsIn&,thread float4& COLOR, ...), and every call site forwards them - the arrangement SPIRV-Cross produces for the same problem. A GLSL global variable that is notconst(the trimming pass demotes dead varyings into one) becomes a local of the entry point, passed the same way. - Coordinate system: the engine renders top-down on every backend (see RhiFwd.h) - clip-space y = +1 is the top row of the target - which is Metal's own convention, so
gl_Positionis returned as it is.gl_FragCoordbecomes the fragment[[position]], counted from the top-left corner like the RHI's viewport and scissor rectangles. - Built-ins: mix/fract/step/smoothstep/clamp/... keep their names; inversesqrt->rsqrt, dFdx/dFdy-> dfdx/dfdy, atan(y,x)->atan2(y,x), mod(a,b)->(a - b*floor(a/b)), radians/degrees are expanded, the relational lessThan()/equal()/... become operators (MSL vector comparisons yield boolN), a vector
==/!=becomesall(a == b)/any(a != b)(GLSL's is a scalar), and mix() with a boolean selector becomes select().
Constructs outside the handled subset (control flow the shared parser declines, a std140 layout MSL cannot reproduce, a non-const global the emitter cannot hoist) make Transform() return false with a diagnostic rather than emit invalid MSL.
Namespaces
- namespace ShaderCompiler
- Offline shader preprocessing tool, see Shader compilation.
Classes
- class ShaderCompiler::MslEmitter
- Transforms an already-lowered modern-GLSL stage source into Metal Shading Language.