diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c703947be6..1e554ba029 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -81,6 +81,7 @@ jobs:
compiler: xcode
compiler_version: "13.3"
python: 3.9
+ test_shaders: ON
- name: Windows_VS2019_Win32_Python27
os: windows-2019
@@ -202,9 +203,10 @@ jobs:
python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --path .. --target glsl
python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --path .. --target osl
python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --path .. --target mdl
+ python Scripts/generateshader.py ../resources/Materials/Examples/StandardSurface --path .. --target msl
working-directory: python
- - name: Shader Validation Tests
+ - name: Shader Validation Tests (Windows)
if: matrix.test_shaders == 'ON' && runner.os == 'Windows'
run: |
vcpkg/vcpkg install glslang --triplet=x64-windows
@@ -212,6 +214,11 @@ jobs:
python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --path . --target glsl --validator glslangValidator.exe --vulkanGlsl True --validatorArgs="-V --aml"
python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --path . --target essl --validator glslangValidator.exe
+ - name: Shader Validation Tests (MacOS)
+ if: matrix.test_shaders == 'ON' && runner.os == 'macOS'
+ run: |
+ python python/Scripts/generateshader.py resources/Materials/Examples/StandardSurface --path . --target msl --validator "xcrun metal --language=metal" --validatorArgs="-w"
+
- name: Static Analysis Tests
if: matrix.static_analysis == 'ON' && runner.os == 'Linux'
run: |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e62a213b84..b24eca1730 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,6 +41,7 @@ option(MATERIALX_BUILD_DOCS "Create HTML documentation using Doxygen. Requires t
option(MATERIALX_BUILD_GEN_GLSL "Build the GLSL shader generator back-end." ON)
option(MATERIALX_BUILD_GEN_OSL "Build the OSL shader generator back-end." ON)
option(MATERIALX_BUILD_GEN_MDL "Build the MDL shader generator back-end." ON)
+option(MATERIALX_BUILD_GEN_MSL "Build the MSL shader generator back-end." ON)
option(MATERIALX_BUILD_RENDER "Build the MaterialX Render modules." ON)
option(MATERIALX_BUILD_OIIO "Build OpenImageIO support for MaterialXRender." OFF)
option(MATERIALX_BUILD_TESTS "Build unit tests." ON)
@@ -105,6 +106,7 @@ mark_as_advanced(MATERIALX_BUILD_DOCS)
mark_as_advanced(MATERIALX_BUILD_GEN_GLSL)
mark_as_advanced(MATERIALX_BUILD_GEN_OSL)
mark_as_advanced(MATERIALX_BUILD_GEN_MDL)
+mark_as_advanced(MATERIALX_BUILD_GEN_MSL)
mark_as_advanced(MATERIALX_BUILD_RENDER)
mark_as_advanced(MATERIALX_BUILD_OIIO)
mark_as_advanced(MATERIALX_BUILD_TESTS)
@@ -235,7 +237,7 @@ add_subdirectory(source/MaterialXFormat)
# Add shader generation subdirectories
add_subdirectory(source/MaterialXGenShader)
-if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MDL)
+if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MDL OR MATERIALX_BUILD_GEN_MSL)
if (MATERIALX_BUILD_GEN_GLSL)
add_definitions(-DMATERIALX_BUILD_GEN_GLSL)
add_subdirectory(source/MaterialXGenGlsl)
@@ -248,6 +250,10 @@ if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MD
add_definitions(-DMATERIALX_BUILD_GEN_MDL)
add_subdirectory(source/MaterialXGenMdl)
endif()
+ if (MATERIALX_BUILD_GEN_MSL)
+ add_definitions(-DMATERIALX_BUILD_GEN_MSL)
+ add_subdirectory(source/MaterialXGenMsl)
+ endif()
add_subdirectory(libraries)
endif()
@@ -258,6 +264,9 @@ if(MATERIALX_BUILD_RENDER)
if (MATERIALX_BUILD_GEN_GLSL)
add_subdirectory(source/MaterialXRenderGlsl)
endif()
+ if (APPLE AND MATERIALX_BUILD_GEN_MSL)
+ add_subdirectory(source/MaterialXRenderMsl)
+ endif()
if (MATERIALX_BUILD_GEN_OSL)
add_subdirectory(source/MaterialXRenderOsl)
endif()
diff --git a/libraries/lights/genmsl/lights_genmsl_impl.mtlx b/libraries/lights/genmsl/lights_genmsl_impl.mtlx
new file mode 100644
index 0000000000..df128e5663
--- /dev/null
+++ b/libraries/lights/genmsl/lights_genmsl_impl.mtlx
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/lights/genmsl/mx_directional_light.metal b/libraries/lights/genmsl/mx_directional_light.metal
new file mode 100644
index 0000000000..8f90b4e807
--- /dev/null
+++ b/libraries/lights/genmsl/mx_directional_light.metal
@@ -0,0 +1,5 @@
+void mx_directional_light(LightData light, float3 position, thread lightshader& result)
+{
+ result.direction = -light.direction;
+ result.intensity = light.color * light.intensity;
+}
diff --git a/libraries/lights/genmsl/mx_point_light.metal b/libraries/lights/genmsl/mx_point_light.metal
new file mode 100644
index 0000000000..b4d5b1292e
--- /dev/null
+++ b/libraries/lights/genmsl/mx_point_light.metal
@@ -0,0 +1,8 @@
+void mx_point_light(LightData light, float3 position, thread lightshader& result)
+{
+ result.direction = light.position - position;
+ float distance = length(result.direction) + M_FLOAT_EPS;
+ float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS);
+ result.intensity = light.color * light.intensity / attenuation;
+ result.direction /= distance;
+}
diff --git a/libraries/lights/genmsl/mx_spot_light.metal b/libraries/lights/genmsl/mx_spot_light.metal
new file mode 100644
index 0000000000..cfcc646c42
--- /dev/null
+++ b/libraries/lights/genmsl/mx_spot_light.metal
@@ -0,0 +1,13 @@
+void mx_spot_light(LightData light, float3 position, thread lightshader& result)
+{
+ result.direction = light.position - position;
+ float distance = length(result.direction) + M_FLOAT_EPS;
+ float attenuation = pow(distance + 1.0, light.decay_rate + M_FLOAT_EPS);
+ result.intensity = light.color * light.intensity / attenuation;
+ result.direction /= distance;
+ float low = min(light.inner_angle, light.outer_angle);
+ float high = light.inner_angle;
+ float cosDir = dot(result.direction, -light.direction);
+ float spotAttenuation = smoothstep(low, high, cosDir);
+ result.intensity *= spotAttenuation;
+}
diff --git a/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl b/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
index f347317fa2..cd165c0615 100644
--- a/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
+++ b/libraries/pbrlib/genglsl/lib/mx_microfacet_specular.glsl
@@ -30,6 +30,26 @@ struct FresnelData
// Refraction
bool refraction;
+
+#ifdef __METAL__
+FresnelData(int _model = 0,
+ vec3 _ior = vec3(0.0f),
+ vec3 _extinction = vec3(0.0f),
+ vec3 _F0 = vec3(0.0f),
+ vec3 _F90 = vec3(0.0f),
+ float _exponent = 0.0f,
+ float _tf_thickness = 0.0f,
+ float _tf_ior = 0.0f,
+ bool _refraction = false) :
+ model(_model),
+ ior(_ior),
+ extinction(_extinction),
+ F0(_F0), F90(_F90), exponent(_exponent),
+ tf_thickness(_tf_thickness),
+ tf_ior(_tf_ior),
+ refraction(_refraction) {}
+#endif
+
};
// https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf
@@ -333,11 +353,12 @@ void mx_fresnel_dielectric_phase_polarized(float cosTheta, float eta1, float eta
// Phase shift due to a conducting material
void mx_fresnel_conductor_phase_polarized(float cosTheta, float eta1, vec3 eta2, vec3 kappa2, out vec3 phiP, out vec3 phiS)
{
- if (kappa2 == vec3(0, 0, 0) && eta2.x == eta2.y && eta2.y == eta2.z) {
+ if (dot(kappa2, kappa2) == 0.0 && eta2.x == eta2.y && eta2.y == eta2.z) {
// Use dielectric formula to increase performance
- mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiP.x, phiS.x);
- phiP = phiP.xxx;
- phiS = phiS.xxx;
+ float phiPx, phiSx;
+ mx_fresnel_dielectric_phase_polarized(cosTheta, eta1, eta2.x, phiPx, phiSx);
+ phiP = vec3(phiPx, phiPx, phiPx);
+ phiS = vec3(phiSx, phiSx, phiSx);
return;
}
vec3 k2 = kappa2 / eta2;
@@ -415,7 +436,6 @@ vec3 mx_fresnel_airy(float cosTheta, vec3 ior, vec3 extinction, float tf_thickne
// Optical path difference
float D = 2.0 * eta2 * d * cosTheta2;
- vec3 Dphi = 2.0 * M_PI * D / vec3(580.0, 550.0, 450.0);
float phi21p, phi21s;
vec3 phi23p, phi23s, r123s, r123p;
diff --git a/libraries/pbrlib/genmsl/pbrlib_genmsl_impl.mtlx b/libraries/pbrlib/genmsl/pbrlib_genmsl_impl.mtlx
new file mode 100644
index 0000000000..1900f1e824
--- /dev/null
+++ b/libraries/pbrlib/genmsl/pbrlib_genmsl_impl.mtlx
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/stdlib/genglsl/lib/mx_transform_color.glsl b/libraries/stdlib/genglsl/lib/mx_transform_color.glsl
index a64e0e90d6..f70275687e 100644
--- a/libraries/stdlib/genglsl/lib/mx_transform_color.glsl
+++ b/libraries/stdlib/genglsl/lib/mx_transform_color.glsl
@@ -13,5 +13,5 @@ vec3 mx_srgb_texture_to_lin_rec709(vec3 color)
bvec3 isAbove = greaterThan(color, vec3(0.04045));
vec3 linSeg = color / 12.92;
vec3 powSeg = pow(max(color + vec3(0.055), vec3(0.0)) / 1.055, vec3(2.4));
- return mix(linSeg, powSeg, isAbove);
+ return mix(linSeg, powSeg, vec3(isAbove));
}
diff --git a/libraries/stdlib/genmsl/lib/mx_math.metal b/libraries/stdlib/genmsl/lib/mx_math.metal
new file mode 100644
index 0000000000..e073ddac9f
--- /dev/null
+++ b/libraries/stdlib/genmsl/lib/mx_math.metal
@@ -0,0 +1,107 @@
+#define M_FLOAT_EPS 1e-8
+
+float mx_square(float x)
+{
+ return x*x;
+}
+
+vec2 mx_square(vec2 x)
+{
+ return x*x;
+}
+
+vec3 mx_square(vec3 x)
+{
+ return x*x;
+}
+
+#ifdef __DECL_GL_MATH_FUNCTIONS__
+
+float radians(float degree) { return (degree * M_PI_F / 180.0f); }
+
+float3x3 inverse(float3x3 m)
+{
+ float n11 = m[0][0], n12 = m[1][0], n13 = m[2][0];
+ float n21 = m[0][1], n22 = m[1][1], n23 = m[2][1];
+ float n31 = m[0][2], n32 = m[1][2], n33 = m[2][2];
+
+ float det = determinant(m);
+ float idet = 1.0f / det;
+
+ float3x3 ret;
+
+ ret[0][0] = idet * (n22 * n33 - n32 * n23);
+ ret[1][0] = idet * (n32 * n13 - n12 * n33);
+ ret[2][0] = idet * (n12 * n23 - n22 * n13);
+
+ ret[0][1] = idet * (n31 * n23 - n21 * n33);
+ ret[1][1] = idet * (n11 * n33 - n31 * n13);
+ ret[2][1] = idet * (n21 * n13 - n11 * n23);
+
+ ret[0][2] = idet * (n21 * n32 - n31 * n22);
+ ret[1][2] = idet * (n31 * n12 - n11 * n32);
+ ret[2][2] = idet * (n11 * n22 - n21 * n12);
+
+ return ret;
+}
+
+float4x4 inverse(float4x4 m)
+{
+ float n11 = m[0][0], n12 = m[1][0], n13 = m[2][0], n14 = m[3][0];
+ float n21 = m[0][1], n22 = m[1][1], n23 = m[2][1], n24 = m[3][1];
+ float n31 = m[0][2], n32 = m[1][2], n33 = m[2][2], n34 = m[3][2];
+ float n41 = m[0][3], n42 = m[1][3], n43 = m[2][3], n44 = m[3][3];
+
+ float t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44;
+ float t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44;
+ float t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44;
+ float t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
+
+ float det = determinant(m);
+ float idet = 1.0f / det;
+
+ float4x4 ret;
+
+ ret[0][0] = t11 * idet;
+ ret[0][1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * idet;
+ ret[0][2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * idet;
+ ret[0][3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * idet;
+
+ ret[1][0] = t12 * idet;
+ ret[1][1] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * idet;
+ ret[1][2] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * idet;
+ ret[1][3] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * idet;
+
+ ret[2][0] = t13 * idet;
+ ret[2][1] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * idet;
+ ret[2][2] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * idet;
+ ret[2][3] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * idet;
+
+ ret[3][0] = t14 * idet;
+ ret[3][1] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * idet;
+ ret[3][2] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * idet;
+ ret[3][3] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * idet;
+
+ return ret;
+}
+
+template
+T1 mod(T1 x, T2 y)
+{
+ return x - y * floor(x/y);
+}
+
+template
+T atan(T y_over_x) { return ::atan(y_over_x); }
+
+template
+T atan(T y, T x) { return ::atan2(y, x); }
+
+#define lessThan(a, b) ((a) < (b))
+#define lessThanEqual(a, b) ((a) <= (b))
+#define greaterThan(a, b) ((a) > (b))
+#define greaterThanEqual(a, b) ((a) >= (b))
+#define equal(a, b) ((a) == (b))
+#define notEqual(a, b) ((a) != (b))
+
+#endif
diff --git a/libraries/stdlib/genmsl/lib/mx_matscalaroperators.metal b/libraries/stdlib/genmsl/lib/mx_matscalaroperators.metal
new file mode 100644
index 0000000000..2b32a45762
--- /dev/null
+++ b/libraries/stdlib/genmsl/lib/mx_matscalaroperators.metal
@@ -0,0 +1,55 @@
+float3x3 operator+(float3x3 a, float b)
+{
+ return a + float3x3(b,b,b,b,b,b,b,b,b);
+}
+
+float4x4 operator+(float4x4 a, float b)
+{
+ return a + float4x4(b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b);
+}
+
+float3x3 operator-(float3x3 a, float b)
+{
+ return a - float3x3(b,b,b,b,b,b,b,b,b);
+}
+
+float4x4 operator-(float4x4 a, float b)
+{
+ return a - float4x4(b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b);
+}
+
+float3x3 operator/(float3x3 a, float3x3 b)
+{
+ for(int i = 0; i < 3; ++i)
+ for(int j = 0; j < 3; ++j)
+ a[i][j] /= b[i][j];
+
+ return a;
+}
+
+float4x4 operator/(float4x4 a, float4x4 b)
+{
+ for(int i = 0; i < 4; ++i)
+ for(int j = 0; j < 4; ++j)
+ a[i][j] /= b[i][j];
+
+ return a;
+}
+
+float3x3 operator/(float3x3 a, float b)
+{
+ for(int i = 0; i < 3; ++i)
+ for(int j = 0; j < 3; ++j)
+ a[i][j] /= b;
+
+ return a;
+}
+
+float4x4 operator/(float4x4 a, float b)
+{
+ for(int i = 0; i < 4; ++i)
+ for(int j = 0; j < 4; ++j)
+ a[i][j] /= b;
+
+ return a;
+}
diff --git a/libraries/stdlib/genmsl/lib/mx_sampling.metal b/libraries/stdlib/genmsl/lib/mx_sampling.metal
new file mode 100644
index 0000000000..293de244a6
--- /dev/null
+++ b/libraries/stdlib/genmsl/lib/mx_sampling.metal
@@ -0,0 +1,91 @@
+// Restrict to 7x7 kernel size for performance reasons
+#define MX_MAX_SAMPLE_COUNT 49
+// Size of all weights for all levels (including level 1)
+#define MX_WEIGHT_ARRAY_SIZE 84
+
+//
+// Function to compute the sample size relative to a texture coordinate
+//
+vec2 mx_compute_sample_size_uv(vec2 uv, float filterSize, float filterOffset)
+{
+ vec2 derivUVx = dFdx(uv) * 0.5f;
+ vec2 derivUVy = dFdy(uv) * 0.5f;
+ float derivX = abs(derivUVx.x) + abs(derivUVy.x);
+ float derivY = abs(derivUVx.y) + abs(derivUVy.y);
+ float sampleSizeU = 2.0f * filterSize * derivX + filterOffset;
+ if (sampleSizeU < 1.0E-05f)
+ sampleSizeU = 1.0E-05f;
+ float sampleSizeV = 2.0f * filterSize * derivY + filterOffset;
+ if (sampleSizeV < 1.0E-05f)
+ sampleSizeV = 1.0E-05f;
+ return vec2(sampleSizeU, sampleSizeV);
+}
+
+//
+// Compute a normal mapped to 0..1 space based on a set of input
+// samples using a Sobel filter.
+//
+vec3 mx_normal_from_samples_sobel(constant float S[9], float _scale)
+{
+ float nx = S[0] - S[2] + (2.0*S[3]) - (2.0*S[5]) + S[6] - S[8];
+ float ny = S[0] + (2.0*S[1]) + S[2] - S[6] - (2.0*S[7]) - S[8];
+ float nz = max(_scale, M_FLOAT_EPS) * sqrt(max(1.0 - nx * nx - ny * ny, M_FLOAT_EPS));
+ vec3 norm = normalize(vec3(nx, ny, nz));
+ return (norm + 1.0) * 0.5;
+}
+
+//
+// Apply filter for float samples S, using weights W.
+// sampleCount should be a square of a odd number in the range { 1, 3, 5, 7 }
+//
+float mx_convolution_float(float S[MX_MAX_SAMPLE_COUNT], constant float W[MX_WEIGHT_ARRAY_SIZE], int offset, int sampleCount)
+{
+ float result = 0.0;
+ for (int i = 0; i < sampleCount; i++)
+ {
+ result += S[i]*W[i+offset];
+ }
+ return result;
+}
+
+//
+// Apply filter for vec2 samples S, using weights W.
+// sampleCount should be a square of a odd number in the range { 1, 3, 5, 7 }
+//
+vec2 mx_convolution_vec2(vec2 S[MX_MAX_SAMPLE_COUNT], constant float W[MX_WEIGHT_ARRAY_SIZE], int offset, int sampleCount)
+{
+ vec2 result = vec2(0.0);
+ for (int i=0; i tex;
+ sampler s;
+ int get_width() { return tex.get_width(); }
+ int get_height() { return tex.get_height(); }
+ int get_num_mip_levels() { return tex.get_num_mip_levels(); }
+};
+
+int get_width(MetalTexture mtlTex) { return mtlTex.get_width(); }
+
+float4 texture(MetalTexture mtlTex, float2 uv)
+{
+ return mtlTex.tex.sample(mtlTex.s, uv);
+}
+
+float4 textureLod(MetalTexture mtlTex, float2 uv, float lod)
+{
+ return mtlTex.tex.sample(mtlTex.s, uv, level(lod));
+}
+
+int2 textureSize(MetalTexture mtlTex, int mipLevel)
+{
+ return int2(mtlTex.get_width(), mtlTex.get_height());
+}
+
+int texture_mips(MetalTexture mtlTex)
+{
+ return mtlTex.tex.get_num_mip_levels();
+}
diff --git a/libraries/stdlib/genmsl/mx_burn_color3.metal b/libraries/stdlib/genmsl/mx_burn_color3.metal
new file mode 100644
index 0000000000..856d79c929
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_burn_color3.metal
@@ -0,0 +1,9 @@
+#include "mx_burn_float.metal"
+
+void mx_burn_color3(vec3 fg, vec3 bg, float mixval, out vec3 result)
+{
+ float f;
+ mx_burn_float(fg.x, bg.x, mixval, f); result.x = f;
+ mx_burn_float(fg.y, bg.y, mixval, f); result.y = f;
+ mx_burn_float(fg.z, bg.z, mixval, f); result.z = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_burn_color4.metal b/libraries/stdlib/genmsl/mx_burn_color4.metal
new file mode 100644
index 0000000000..48dc6bfa47
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_burn_color4.metal
@@ -0,0 +1,10 @@
+#include "mx_burn_float.metal"
+
+void mx_burn_color4(vec4 fg, vec4 bg, float mixval, out vec4 result)
+{
+ float f;
+ mx_burn_float(fg.x, bg.x, mixval, f); result.x = f;
+ mx_burn_float(fg.y, bg.y, mixval, f); result.y = f;
+ mx_burn_float(fg.z, bg.z, mixval, f); result.z = f;
+ mx_burn_float(fg.w, bg.w, mixval, f); result.w = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_burn_float.metal b/libraries/stdlib/genmsl/mx_burn_float.metal
new file mode 100644
index 0000000000..31d981ddb9
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_burn_float.metal
@@ -0,0 +1,9 @@
+void mx_burn_float(float fg, float bg, float mixval, out float result)
+{
+ if (abs(fg) < M_FLOAT_EPS)
+ {
+ result = 0.0;
+ return;
+ }
+ result = mixval*(1.0 - ((1.0 - bg) / fg)) + ((1.0-mixval)*bg);
+}
diff --git a/libraries/stdlib/genmsl/mx_dodge_color3.metal b/libraries/stdlib/genmsl/mx_dodge_color3.metal
new file mode 100644
index 0000000000..864a6a2529
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_dodge_color3.metal
@@ -0,0 +1,9 @@
+#include "mx_dodge_float.metal"
+
+void mx_dodge_color3(vec3 fg, vec3 bg, float mixval, out vec3 result)
+{
+ float f;
+ mx_dodge_float(fg.x, bg.x, mixval, f); result.x = f;
+ mx_dodge_float(fg.y, bg.y, mixval, f); result.y = f;
+ mx_dodge_float(fg.z, bg.z, mixval, f); result.z = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_dodge_color4.metal b/libraries/stdlib/genmsl/mx_dodge_color4.metal
new file mode 100644
index 0000000000..dc226b4271
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_dodge_color4.metal
@@ -0,0 +1,10 @@
+#include "mx_dodge_float.metal"
+
+void mx_dodge_color4(vec4 fg , vec4 bg , float mixval, out vec4 result)
+{
+ float f;
+ mx_dodge_float(fg.x, bg.x, mixval, f); result.x = f;
+ mx_dodge_float(fg.y, bg.y, mixval, f); result.y = f;
+ mx_dodge_float(fg.z, bg.z, mixval, f); result.z = f;
+ mx_dodge_float(fg.w, bg.w, mixval, f); result.w = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_dodge_float.metal b/libraries/stdlib/genmsl/mx_dodge_float.metal
new file mode 100644
index 0000000000..f138354138
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_dodge_float.metal
@@ -0,0 +1,9 @@
+void mx_dodge_float(float fg, float bg, float mixval, out float result)
+{
+ if (abs(1.0 - fg) < M_FLOAT_EPS)
+ {
+ result = 0.0;
+ return;
+ }
+ result = mixval*(bg / (1.0 - fg)) + ((1.0-mixval)*bg);
+}
diff --git a/libraries/stdlib/genmsl/mx_normalmap.metal b/libraries/stdlib/genmsl/mx_normalmap.metal
new file mode 100644
index 0000000000..c9c7bd5546
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_normalmap.metal
@@ -0,0 +1,16 @@
+void mx_normalmap(vec3 value, int map_space, float normal_scale, vec3 N, vec3 T, out vec3 result)
+{
+ // Decode the normal map.
+ value = all(value == vec3(0.0f)) ? vec3(0.0, 0.0, 1.0) : value * 2.0 - 1.0;
+
+ // Transform from tangent space if needed.
+ if (map_space == 0)
+ {
+ vec3 B = normalize(cross(N, T));
+ value.xy *= normal_scale;
+ value = T * value.x + B * value.y + N * value.z;
+ }
+
+ // Normalize the result.
+ result = normalize(value);
+}
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_float.metal b/libraries/stdlib/genmsl/mx_smoothstep_float.metal
new file mode 100644
index 0000000000..1bca2e4d9b
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_float.metal
@@ -0,0 +1,9 @@
+void mx_smoothstep_float(float val, float low, float high, out float result)
+{
+ if (val <= low)
+ result = 0.0;
+ else if (val >= high)
+ result = 1.0;
+ else
+ result = smoothstep(low, high, val);
+}
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_vec2.metal b/libraries/stdlib/genmsl/mx_smoothstep_vec2.metal
new file mode 100644
index 0000000000..0baa763137
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_vec2.metal
@@ -0,0 +1,8 @@
+#include "mx_smoothstep_float.metal"
+
+void mx_smoothstep_vec2(vec2 val, vec2 low, vec2 high, out vec2 result)
+{
+ float f;
+ mx_smoothstep_float(val.x, low.x, high.x, f); result.x = f;
+ mx_smoothstep_float(val.y, low.y, high.y, f); result.y = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_vec2FA.metal b/libraries/stdlib/genmsl/mx_smoothstep_vec2FA.metal
new file mode 100644
index 0000000000..a05a10d746
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_vec2FA.metal
@@ -0,0 +1,8 @@
+#include "mx_smoothstep_float.metal"
+
+void mx_smoothstep_vec2FA(vec2 val, float low, float high, out vec2 result)
+{
+ float f;
+ mx_smoothstep_float(val.x, low, high, f); result.x = f;
+ mx_smoothstep_float(val.y, low, high, f); result.y = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_vec3.metal b/libraries/stdlib/genmsl/mx_smoothstep_vec3.metal
new file mode 100644
index 0000000000..b0f969751b
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_vec3.metal
@@ -0,0 +1,9 @@
+#include "mx_smoothstep_float.metal"
+
+void mx_smoothstep_vec3(vec3 val, vec3 low, vec3 high, thread vec3& result)
+ {
+ float f;
+ mx_smoothstep_float(val.x, low.x, high.x, f); result.x = f;
+ mx_smoothstep_float(val.y, low.y, high.y, f); result.y = f;
+ mx_smoothstep_float(val.z, low.z, high.z, f); result.z = f;
+ }
\ No newline at end of file
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_vec3FA.metal b/libraries/stdlib/genmsl/mx_smoothstep_vec3FA.metal
new file mode 100644
index 0000000000..4a1922d5af
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_vec3FA.metal
@@ -0,0 +1,9 @@
+#include "mx_smoothstep_float.metal"
+
+void mx_smoothstep_vec3FA(vec3 val, float low, float high, out vec3 result)
+{
+ float f;
+ mx_smoothstep_float(val.x, low, high, f); result.x = f;
+ mx_smoothstep_float(val.y, low, high, f); result.y = f;
+ mx_smoothstep_float(val.z, low, high, f); result.z = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_vec4.metal b/libraries/stdlib/genmsl/mx_smoothstep_vec4.metal
new file mode 100644
index 0000000000..8bf2f3d025
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_vec4.metal
@@ -0,0 +1,10 @@
+#include "mx_smoothstep_float.metal"
+
+void mx_smoothstep_vec4(vec4 val, vec4 low, vec4 high, out vec4 result)
+{
+ float f;
+ mx_smoothstep_float(val.x, low.x, high.x, f); result.x = f;
+ mx_smoothstep_float(val.y, low.y, high.y, f); result.y = f;
+ mx_smoothstep_float(val.z, low.z, high.z, f); result.z = f;
+ mx_smoothstep_float(val.w, low.w, high.w, f); result.w = f;
+}
diff --git a/libraries/stdlib/genmsl/mx_smoothstep_vec4FA.metal b/libraries/stdlib/genmsl/mx_smoothstep_vec4FA.metal
new file mode 100644
index 0000000000..a7c27cb0e6
--- /dev/null
+++ b/libraries/stdlib/genmsl/mx_smoothstep_vec4FA.metal
@@ -0,0 +1,10 @@
+#include "mx_smoothstep_float.metal"
+
+void mx_smoothstep_vec4FA(vec4 val, float low, float high, out vec4 result)
+{
+ float f;
+ mx_smoothstep_float(val.x, low, high, f); result.x = f;
+ mx_smoothstep_float(val.y, low, high, f); result.y = f;
+ mx_smoothstep_float(val.z, low, high, f); result.z = f;
+ mx_smoothstep_float(val.w, low, high, f); result.w = f;
+}
diff --git a/libraries/stdlib/genmsl/stdlib_genmsl_cm_impl.mtlx b/libraries/stdlib/genmsl/stdlib_genmsl_cm_impl.mtlx
new file mode 100644
index 0000000000..52f8dd5c3d
--- /dev/null
+++ b/libraries/stdlib/genmsl/stdlib_genmsl_cm_impl.mtlx
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx b/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx
new file mode 100644
index 0000000000..bc2a539b54
--- /dev/null
+++ b/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx
@@ -0,0 +1,790 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/stdlib/genmsl/stdlib_genmsl_unit_impl.mtlx b/libraries/stdlib/genmsl/stdlib_genmsl_unit_impl.mtlx
new file mode 100644
index 0000000000..da1d433bef
--- /dev/null
+++ b/libraries/stdlib/genmsl/stdlib_genmsl_unit_impl.mtlx
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/targets/genmsl.mtlx b/libraries/targets/genmsl.mtlx
new file mode 100644
index 0000000000..d18c45ca58
--- /dev/null
+++ b/libraries/targets/genmsl.mtlx
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/Scripts/baketextures.py b/python/Scripts/baketextures.py
index d96a01261b..be28553de7 100644
--- a/python/Scripts/baketextures.py
+++ b/python/Scripts/baketextures.py
@@ -4,11 +4,14 @@
'''
import sys, os, argparse
+from sys import platform
import MaterialX as mx
from MaterialX import PyMaterialXGenShader
from MaterialX import PyMaterialXGenGlsl
from MaterialX import PyMaterialXRender as mx_render
from MaterialX import PyMaterialXRenderGlsl as mx_render_glsl
+if platform == "darwin":
+ from MaterialX import PyMaterialXRenderMsl as mx_render_msl
def main():
parser = argparse.ArgumentParser(description="Generate a baked version of each material in the input document.")
@@ -18,6 +21,8 @@ def main():
parser.add_argument("--average", dest="average", action="store_true", help="Average baked images to generate constant values.")
parser.add_argument("--path", dest="paths", action='append', nargs='+', help="An additional absolute search path location (e.g. '/projects/MaterialX')")
parser.add_argument("--library", dest="libraries", action='append', nargs='+', help="An additional relative path to a custom data library folder (e.g. 'libraries/custom')")
+ if platform == "darwin":
+ parser.add_argument("--glsl", dest="useGlslBackend", default=False, type=bool, help="Set to True to use GLSL backend (default = Metal).")
parser.add_argument(dest="inputFilename", help="Filename of the input document.")
parser.add_argument(dest="outputFilename", help="Filename of the output document.")
opts = parser.parse_args()
@@ -52,7 +57,13 @@ def main():
print(msg)
baseType = mx_render.BaseType.FLOAT if opts.hdr else mx_render.BaseType.UINT8
- baker = mx_render_glsl.TextureBaker.create(opts.width, opts.height, baseType)
+
+
+ if platform == "darwin" and not opts.useGlslBackend:
+ baker = mx_render_msl.TextureBaker.create(opts.width, opts.height, baseType)
+ else:
+ baker = mx_render_glsl.TextureBaker.create(opts.width, opts.height, baseType)
+
if opts.average:
baker.setAverageImages(True)
baker.bakeAllMaterials(doc, searchPath, opts.outputFilename)
diff --git a/python/Scripts/generateshader.py b/python/Scripts/generateshader.py
index e56b27b4a6..86a67d96a8 100644
--- a/python/Scripts/generateshader.py
+++ b/python/Scripts/generateshader.py
@@ -10,13 +10,18 @@
import MaterialX.PyMaterialXGenGlsl as mx_gen_glsl
import MaterialX.PyMaterialXGenOsl as mx_gen_osl
import MaterialX.PyMaterialXGenMdl as mx_gen_mdl
+import MaterialX.PyMaterialXGenMsl as mx_gen_msl
def validateCode(sourceCodeFile, codevalidator, codevalidatorArgs):
if codevalidator:
- cmd = codevalidator + ' ' + sourceCodeFile
+ cmd = codevalidator.split()
+ cmd.append(sourceCodeFile)
if codevalidatorArgs:
- cmd += ' ' + codevalidatorArgs
- print('----- Run Validator: '+ cmd)
+ cmd.append(codevalidatorArgs)
+ cmd_flatten ='----- Run Validator: '
+ for c in cmd:
+ cmd_flatten += c + ' '
+ print(cmd_flatten)
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
result = output.decode(encoding='utf-8')
@@ -96,6 +101,8 @@ def main():
shadergen = mx_gen_glsl.EsslShaderGenerator.create()
elif gentarget == 'vulkan':
shadergen = mx_gen_glsl.VkShaderGenerator.create()
+ elif gentarget == 'msl':
+ shadergen = mx_gen_msl.MslShaderGenerator.create()
else:
shadergen = mx_gen_glsl.GlslShaderGenerator.create()
@@ -153,7 +160,7 @@ def main():
if shader:
# Use extension of .vert and .frag as it's type is
# recognized by glslangValidator
- if gentarget in ['glsl', 'essl', 'vulkan']:
+ if gentarget in ['glsl', 'essl', 'vulkan', 'msl']:
pixelSource = shader.getSourceCode(mx_gen_shader.PIXEL_STAGE)
filename = pathPrefix + "/" + shader.getName() + "." + gentarget + ".frag"
print('--- Wrote pixel shader to: ' + filename)
diff --git a/python/Scripts/translateshader.py b/python/Scripts/translateshader.py
index 518ddc4ba5..d055e25837 100644
--- a/python/Scripts/translateshader.py
+++ b/python/Scripts/translateshader.py
@@ -7,10 +7,14 @@
import sys, os, argparse
import MaterialX as mx
+from sys import platform
from MaterialX import PyMaterialXGenShader as mx_gen_shader
from MaterialX import PyMaterialXGenGlsl as ms_gen_glsl
from MaterialX import PyMaterialXRender as mx_render
from MaterialX import PyMaterialXRenderGlsl as mx_render_glsl
+if platform == "darwin":
+ from MaterialX import PyMaterialXGenMsl as ms_gen_msl
+ from MaterialX import PyMaterialXRenderMsl as mx_render_msl
def main():
parser = argparse.ArgumentParser(description="Generate a translated baked version of each material in the input document.")
@@ -19,6 +23,8 @@ def main():
parser.add_argument("--hdr", dest="hdr", action="store_true", help="Bake images with high dynamic range (e.g. in HDR or EXR format).")
parser.add_argument("--path", dest="paths", action='append', nargs='+', help="An additional absolute search path location (e.g. '/projects/MaterialX')")
parser.add_argument("--library", dest="libraries", action='append', nargs='+', help="An additional relative path to a custom data library folder (e.g. 'libraries/custom')")
+ if platform == "darwin":
+ parser.add_argument("--glsl", dest="useGlslBackend", default=False, type=bool, help="Set to True to use GLSL backend (default = Metal).")
parser.add_argument(dest="inputFilename", help="Filename of the input document.")
parser.add_argument(dest="outputFilename", help="Filename of the output document.")
parser.add_argument(dest="destShader", help="Destination shader for translation")
@@ -85,7 +91,10 @@ def main():
# Bake translated materials to flat textures.
baseType = mx_render.BaseType.FLOAT if opts.hdr else mx_render.BaseType.UINT8
- baker = mx_render_glsl.TextureBaker.create(bakeWidth, bakeHeight, baseType)
+ if platform == "darwin" and not opts.useGlslBackend:
+ baker = mx_render_msl.TextureBaker.create(bakeWidth, bakeHeight, baseType)
+ else:
+ baker = mx_render_glsl.TextureBaker.create(bakeWidth, bakeHeight, baseType)
baker.bakeAllMaterials(doc, searchPath, opts.outputFilename)
if __name__ == '__main__':
diff --git a/resources/Lights/envmap_shader.mtlx b/resources/Lights/envmap_shader.mtlx
index feab1949df..2f6ff52a23 100644
--- a/resources/Lights/envmap_shader.mtlx
+++ b/resources/Lights/envmap_shader.mtlx
@@ -7,7 +7,7 @@
-
+
diff --git a/resources/Materials/TestSuite/_options.mtlx b/resources/Materials/TestSuite/_options.mtlx
index 97b2252487..3f09c9bb3d 100644
--- a/resources/Materials/TestSuite/_options.mtlx
+++ b/resources/Materials/TestSuite/_options.mtlx
@@ -38,7 +38,7 @@
-
+
diff --git a/source/MaterialXFormat/File.cpp b/source/MaterialXFormat/File.cpp
index 1df948fdb8..bb40ba54d1 100644
--- a/source/MaterialXFormat/File.cpp
+++ b/source/MaterialXFormat/File.cpp
@@ -339,7 +339,7 @@ FilePath FilePath::getModulePath()
vector buf(PATH_MAX);
while (true)
{
- uint32_t reqSize = buf.size();
+ uint32_t reqSize = static_cast(buf.size());
if (_NSGetExecutablePath(buf.data(), &reqSize) == -1)
{
buf.resize((size_t) reqSize);
diff --git a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp
index f4260e6f03..e9919b7460 100644
--- a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp
+++ b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp
@@ -57,93 +57,113 @@ GlslShaderGenerator::GlslShaderGenerator() :
// Register all custom node implementation classes
//
+ StringVec elementNames;
+
//
- //
- registerImplementation("IM_switch_float_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_color3_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_color4_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_vector2_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_vector3_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_vector4_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- //
- registerImplementation("IM_switch_floatI_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_color3I_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_color4I_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_vector2I_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_vector3I_" + GlslShaderGenerator::TARGET, SwitchNode::create);
- registerImplementation("IM_switch_vector4I_" + GlslShaderGenerator::TARGET, SwitchNode::create);
+ elementNames = {
+ //
+ "IM_switch_float_" + GlslShaderGenerator::TARGET,
+ "IM_switch_color3_" + GlslShaderGenerator::TARGET,
+ "IM_switch_color4_" + GlslShaderGenerator::TARGET,
+ "IM_switch_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_switch_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_switch_vector4_" + GlslShaderGenerator::TARGET,
+
+ //
+ "IM_switch_floatI_" + GlslShaderGenerator::TARGET,
+ "IM_switch_color3I_" + GlslShaderGenerator::TARGET,
+ "IM_switch_color4I_" + GlslShaderGenerator::TARGET,
+ "IM_switch_vector2I_" + GlslShaderGenerator::TARGET,
+ "IM_switch_vector3I_" + GlslShaderGenerator::TARGET,
+ "IM_switch_vector4I_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, SwitchNode::create);
//
- //
- registerImplementation("IM_swizzle_float_color3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_float_color4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_float_vector2_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_float_vector3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_float_vector4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- //
- registerImplementation("IM_swizzle_color3_float_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color3_color3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color3_color4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color3_vector2_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color3_vector3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color3_vector4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- //
- registerImplementation("IM_swizzle_color4_float_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color4_color3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color4_color4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color4_vector2_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color4_vector3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_color4_vector4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- //
- registerImplementation("IM_swizzle_vector2_float_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector2_color3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector2_color4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector2_vector2_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector2_vector3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector2_vector4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- //
- registerImplementation("IM_swizzle_vector3_float_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector3_color3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector3_color4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector3_vector2_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector3_vector3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector3_vector4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- //
- registerImplementation("IM_swizzle_vector4_float_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector4_color3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector4_color4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector4_vector2_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector4_vector3_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
- registerImplementation("IM_swizzle_vector4_vector4_" + GlslShaderGenerator::TARGET, SwizzleNode::create);
+ elementNames = {
+ //
+ "IM_swizzle_float_color3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_float_color4_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_float_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_float_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_float_vector4_" + GlslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_color3_float_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color3_color3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color3_color4_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color3_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color3_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color3_vector4_" + GlslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_color4_float_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color4_color3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color4_color4_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color4_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color4_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_color4_vector4_" + GlslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_vector2_float_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_color3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_color4_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_vector4_" + GlslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_vector3_float_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_color3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_color4_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_vector4_" + GlslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_vector4_float_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_color3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_color4_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_vector4_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, SwizzleNode::create);
//
- registerImplementation("IM_convert_float_color3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_float_color4_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_float_vector2_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_float_vector3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_float_vector4_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_vector2_vector3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_vector3_vector2_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_vector3_color3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_vector3_vector4_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_vector4_vector3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_vector4_color4_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_color3_vector3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_color4_vector4_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_color3_color4_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_color4_color3_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_boolean_float_" + GlslShaderGenerator::TARGET, ConvertNode::create);
- registerImplementation("IM_convert_integer_float_" + GlslShaderGenerator::TARGET, ConvertNode::create);
+ elementNames = {
+ "IM_convert_float_color3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_float_color4_" + GlslShaderGenerator::TARGET,
+ "IM_convert_float_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_convert_float_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_float_vector4_" + GlslShaderGenerator::TARGET,
+ "IM_convert_vector2_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_vector3_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_convert_vector3_color3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_vector3_vector4_" + GlslShaderGenerator::TARGET,
+ "IM_convert_vector4_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_vector4_color4_" + GlslShaderGenerator::TARGET,
+ "IM_convert_color3_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_color4_vector4_" + GlslShaderGenerator::TARGET,
+ "IM_convert_color3_color4_" + GlslShaderGenerator::TARGET,
+ "IM_convert_color4_color3_" + GlslShaderGenerator::TARGET,
+ "IM_convert_boolean_float_" + GlslShaderGenerator::TARGET,
+ "IM_convert_integer_float_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, ConvertNode::create);
//
- registerImplementation("IM_combine2_vector2_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine2_color4CF_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine2_vector4VF_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine2_vector4VV_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine3_color3_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine3_vector3_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine4_color4_" + GlslShaderGenerator::TARGET, CombineNode::create);
- registerImplementation("IM_combine4_vector4_" + GlslShaderGenerator::TARGET, CombineNode::create);
+ elementNames = {
+ "IM_combine2_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_combine2_color4CF_" + GlslShaderGenerator::TARGET,
+ "IM_combine2_vector4VF_" + GlslShaderGenerator::TARGET,
+ "IM_combine2_vector4VV_" + GlslShaderGenerator::TARGET,
+ "IM_combine3_color3_" + GlslShaderGenerator::TARGET,
+ "IM_combine3_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_combine4_color4_" + GlslShaderGenerator::TARGET,
+ "IM_combine4_vector4_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, CombineNode::create);
//
registerImplementation("IM_position_vector3_" + GlslShaderGenerator::TARGET, PositionNodeGlsl::create);
@@ -161,15 +181,18 @@ GlslShaderGenerator::GlslShaderGenerator() :
registerImplementation("IM_geomcolor_color3_" + GlslShaderGenerator::TARGET, GeomColorNodeGlsl::create);
registerImplementation("IM_geomcolor_color4_" + GlslShaderGenerator::TARGET, GeomColorNodeGlsl::create);
//
- registerImplementation("IM_geompropvalue_integer_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
+ elementNames = {
+ "IM_geompropvalue_integer_" + GlslShaderGenerator::TARGET,
+ "IM_geompropvalue_float_" + GlslShaderGenerator::TARGET,
+ "IM_geompropvalue_color3_" + GlslShaderGenerator::TARGET,
+ "IM_geompropvalue_color4_" + GlslShaderGenerator::TARGET,
+ "IM_geompropvalue_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_geompropvalue_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_geompropvalue_vector4_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, GeomPropValueNodeGlsl::create);
registerImplementation("IM_geompropvalue_boolean_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlslAsUniform::create);
registerImplementation("IM_geompropvalue_string_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlslAsUniform::create);
- registerImplementation("IM_geompropvalue_float_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
- registerImplementation("IM_geompropvalue_color3_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
- registerImplementation("IM_geompropvalue_color4_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
- registerImplementation("IM_geompropvalue_vector2_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
- registerImplementation("IM_geompropvalue_vector3_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
- registerImplementation("IM_geompropvalue_vector4_" + GlslShaderGenerator::TARGET, GeomPropValueNodeGlsl::create);
//
registerImplementation("IM_frame_float_" + GlslShaderGenerator::TARGET, FrameNodeGlsl::create);
@@ -194,12 +217,15 @@ GlslShaderGenerator::GlslShaderGenerator() :
registerImplementation("IM_heighttonormal_vector3_" + GlslShaderGenerator::TARGET, HeightToNormalNodeGlsl::create);
//
- registerImplementation("IM_blur_float_" + GlslShaderGenerator::TARGET, BlurNodeGlsl::create);
- registerImplementation("IM_blur_color3_" + GlslShaderGenerator::TARGET, BlurNodeGlsl::create);
- registerImplementation("IM_blur_color4_" + GlslShaderGenerator::TARGET, BlurNodeGlsl::create);
- registerImplementation("IM_blur_vector2_" + GlslShaderGenerator::TARGET, BlurNodeGlsl::create);
- registerImplementation("IM_blur_vector3_" + GlslShaderGenerator::TARGET, BlurNodeGlsl::create);
- registerImplementation("IM_blur_vector4_" + GlslShaderGenerator::TARGET, BlurNodeGlsl::create);
+ elementNames = {
+ "IM_blur_float_" + GlslShaderGenerator::TARGET,
+ "IM_blur_color3_" + GlslShaderGenerator::TARGET,
+ "IM_blur_color4_" + GlslShaderGenerator::TARGET,
+ "IM_blur_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_blur_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_blur_vector4_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, BlurNodeGlsl::create);
//
- registerImplementation("IM_image_float_" + GlslShaderGenerator::TARGET, HwImageNode::create);
- registerImplementation("IM_image_color3_" + GlslShaderGenerator::TARGET, HwImageNode::create);
- registerImplementation("IM_image_color4_" + GlslShaderGenerator::TARGET, HwImageNode::create);
- registerImplementation("IM_image_vector2_" + GlslShaderGenerator::TARGET, HwImageNode::create);
- registerImplementation("IM_image_vector3_" + GlslShaderGenerator::TARGET, HwImageNode::create);
- registerImplementation("IM_image_vector4_" + GlslShaderGenerator::TARGET, HwImageNode::create);
+ elementNames = {
+ "IM_image_float_" + GlslShaderGenerator::TARGET,
+ "IM_image_color3_" + GlslShaderGenerator::TARGET,
+ "IM_image_color4_" + GlslShaderGenerator::TARGET,
+ "IM_image_vector2_" + GlslShaderGenerator::TARGET,
+ "IM_image_vector3_" + GlslShaderGenerator::TARGET,
+ "IM_image_vector4_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, HwImageNode::create);
//
registerImplementation("IM_layer_bsdf_" + GlslShaderGenerator::TARGET, ClosureLayerNode::create);
@@ -228,10 +257,13 @@ GlslShaderGenerator::GlslShaderGenerator() :
registerImplementation("IM_add_bsdf_" + GlslShaderGenerator::TARGET, ClosureAddNode::create);
registerImplementation("IM_add_edf_" + GlslShaderGenerator::TARGET, ClosureAddNode::create);
//
- registerImplementation("IM_multiply_bsdfC_" + GlslShaderGenerator::TARGET, ClosureMultiplyNode::create);
- registerImplementation("IM_multiply_bsdfF_" + GlslShaderGenerator::TARGET, ClosureMultiplyNode::create);
- registerImplementation("IM_multiply_edfC_" + GlslShaderGenerator::TARGET, ClosureMultiplyNode::create);
- registerImplementation("IM_multiply_edfF_" + GlslShaderGenerator::TARGET, ClosureMultiplyNode::create);
+ elementNames = {
+ "IM_multiply_bsdfC_" + GlslShaderGenerator::TARGET,
+ "IM_multiply_bsdfF_" + GlslShaderGenerator::TARGET,
+ "IM_multiply_edfC_" + GlslShaderGenerator::TARGET,
+ "IM_multiply_edfF_" + GlslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, ClosureMultiplyNode::create);
//
registerImplementation("IM_thin_film_bsdf_" + GlslShaderGenerator::TARGET, NopNode::create);
diff --git a/source/MaterialXGenMsl/CMakeLists.txt b/source/MaterialXGenMsl/CMakeLists.txt
new file mode 100644
index 0000000000..31d2599531
--- /dev/null
+++ b/source/MaterialXGenMsl/CMakeLists.txt
@@ -0,0 +1,44 @@
+file(GLOB_RECURSE materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
+file(GLOB_RECURSE materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*")
+
+assign_source_group("Source Files" ${materialx_source})
+assign_source_group("Header Files" ${materialx_headers})
+
+add_library(MaterialXGenMsl ${materialx_source} ${materialx_headers})
+
+add_definitions(-DMATERIALX_GENMSL_EXPORTS)
+
+set_target_properties(
+ MaterialXGenMsl PROPERTIES
+ OUTPUT_NAME MaterialXGenMsl${MATERIALX_LIBNAME_SUFFIX}
+ COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}"
+ LINK_FLAGS "${EXTERNAL_LINK_FLAGS}"
+ INSTALL_RPATH "${MATERIALX_SAME_DIR_RPATH}"
+ VERSION "${MATERIALX_LIBRARY_VERSION}"
+ SOVERSION "${MATERIALX_MAJOR_VERSION}")
+
+target_link_libraries(
+ MaterialXGenMsl
+ MaterialXGenShader
+ MaterialXCore
+ ${CMAKE_DL_LIBS})
+
+target_include_directories(MaterialXGenMsl
+ PUBLIC
+ $
+ $
+ PRIVATE
+ ${EXTERNAL_INCLUDE_DIRS})
+
+install(TARGETS MaterialXGenMsl
+ EXPORT MaterialX
+ ARCHIVE DESTINATION ${MATERIALX_INSTALL_LIB_PATH}
+ LIBRARY DESTINATION ${MATERIALX_INSTALL_LIB_PATH}
+ RUNTIME DESTINATION bin)
+
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
+ DESTINATION ${MATERIALX_INSTALL_INCLUDE_PATH}/MaterialXGenMsl/ MESSAGE_NEVER
+ FILES_MATCHING PATTERN "*.h*")
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/MaterialXGenMsl.pdb"
+ DESTINATION "${MATERIALX_INSTALL_LIB_PATH}/" OPTIONAL)
diff --git a/source/MaterialXGenMsl/Export.h b/source/MaterialXGenMsl/Export.h
new file mode 100644
index 0000000000..afaa791efa
--- /dev/null
+++ b/source/MaterialXGenMsl/Export.h
@@ -0,0 +1,22 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_GENMSL_EXPORT_H
+#define MATERIALX_GENMSL_EXPORT_H
+
+#include
+
+/// @file
+/// Macros for declaring imported and exported symbols.
+
+#if defined(MATERIALX_GENMSL_EXPORTS)
+ #define MX_GENMSL_API MATERIALX_SYMBOL_EXPORT
+ #define MX_GENMSL_EXTERN_TEMPLATE(...) MATERIALX_EXPORT_EXTERN_TEMPLATE(__VA_ARGS__)
+#else
+ #define MX_GENMSL_API MATERIALX_SYMBOL_IMPORT
+ #define MX_GENMSL_EXTERN_TEMPLATE(...) MATERIALX_IMPORT_EXTERN_TEMPLATE(__VA_ARGS__)
+#endif
+
+#endif
diff --git a/source/MaterialXGenMsl/MslResourceBindingContext.cpp b/source/MaterialXGenMsl/MslResourceBindingContext.cpp
new file mode 100644
index 0000000000..f372c428eb
--- /dev/null
+++ b/source/MaterialXGenMsl/MslResourceBindingContext.cpp
@@ -0,0 +1,148 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+//
+// MslResourceBindingContext
+//
+MslResourceBindingContext::MslResourceBindingContext(
+ size_t uniformBindingLocation, size_t samplerBindingLocation) :
+ _hwInitUniformBindLocation(uniformBindingLocation),
+ _hwInitSamplerBindLocation(samplerBindingLocation)
+{}
+
+void MslResourceBindingContext::initialize()
+{
+ // Reset bind location counter.
+ _hwUniformBindLocation = _hwInitUniformBindLocation;
+
+ // Reset sampler bind location counter.
+ _hwSamplerBindLocation = _hwInitSamplerBindLocation;
+}
+
+void MslResourceBindingContext::emitDirectives(GenContext&, ShaderStage&)
+{
+}
+
+void MslResourceBindingContext::emitResourceBindings(GenContext& context, const VariableBlock& uniforms, ShaderStage& stage)
+{
+ ShaderGenerator& generator = context.getShaderGenerator();
+
+ // First, emit all value uniforms in a block with single layout binding
+ bool hasValueUniforms = false;
+ for (auto uniform : uniforms.getVariableOrder())
+ {
+ if (uniform->getType() != Type::FILENAME)
+ {
+ hasValueUniforms = true;
+ break;
+ }
+ }
+ if (hasValueUniforms)
+ {
+ generator.emitLine("struct " + uniforms.getName(),
+ stage, false);
+ generator.emitScopeBegin(stage);
+ for (auto uniform : uniforms.getVariableOrder())
+ {
+ if (uniform->getType() != Type::FILENAME)
+ {
+ generator.emitLineBegin(stage);
+ generator.emitVariableDeclaration(uniform, EMPTY_STRING, context, stage, false);
+ generator.emitString(Syntax::SEMICOLON, stage);
+ generator.emitLineEnd(stage, false);
+ }
+ }
+ generator.emitScopeEnd(stage, true);
+ }
+
+ generator.emitLineBreak(stage);
+}
+
+void MslResourceBindingContext::emitStructuredResourceBindings(GenContext& context, const VariableBlock& uniforms,
+ ShaderStage& stage, const std::string& structInstanceName,
+ const std::string& arraySuffix)
+{
+ ShaderGenerator& generator = context.getShaderGenerator();
+
+ const size_t baseAlignment = 16;
+ // Values are adjusted based on
+ // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
+ std::unordered_map alignmentMap({
+ { Type::FLOAT, baseAlignment / 4 },
+ { Type::INTEGER, baseAlignment / 4 },
+ { Type::BOOLEAN, baseAlignment / 4 },
+ { Type::COLOR3, baseAlignment },
+ { Type::COLOR4, baseAlignment },
+ { Type::VECTOR2, baseAlignment / 2 },
+ { Type::VECTOR3, baseAlignment },
+ { Type::VECTOR4, baseAlignment },
+ { Type::MATRIX33, baseAlignment * 4 },
+ { Type::MATRIX44, baseAlignment * 4 } });
+
+ // Get struct alignment and size
+ // alignment, uniform member index
+ vector> memberOrder;
+ size_t structSize = 0;
+ for (size_t i = 0; i < uniforms.size(); ++i)
+ {
+ auto it = alignmentMap.find(uniforms[i]->getType());
+ if (it == alignmentMap.end())
+ {
+ structSize += baseAlignment;
+ memberOrder.push_back(std::make_pair(baseAlignment, i));
+ }
+ else
+ {
+ structSize += it->second;
+ memberOrder.push_back(std::make_pair(it->second, i));
+ }
+ }
+
+ // Align up and determine number of padding floats to add
+ const size_t numPaddingfloats =
+ (((structSize + (baseAlignment - 1)) & ~(baseAlignment - 1)) - structSize) / 4;
+
+ // Sort order from largest to smallest
+ std::sort(memberOrder.begin(), memberOrder.end(),
+ [](const std::pair& a, const std::pair& b) {
+ return a.first > b.first;
+ });
+
+ // Emit the struct
+ generator.emitLine("struct " + uniforms.getName(), stage, false);
+ generator.emitScopeBegin(stage);
+
+ for (size_t i = 0; i < uniforms.size(); ++i)
+ {
+ size_t variableIndex = memberOrder[i].second;
+ generator.emitLineBegin(stage);
+ generator.emitVariableDeclaration(
+ uniforms[variableIndex], EMPTY_STRING, context, stage, false);
+ generator.emitString(Syntax::SEMICOLON, stage);
+ generator.emitLineEnd(stage, false);
+ }
+
+ // Emit padding
+ for (size_t i = 0; i < numPaddingfloats; ++i)
+ {
+ generator.emitLine("float pad" + std::to_string(i), stage, true);
+ }
+ generator.emitScopeEnd(stage, true);
+
+
+ // Emit binding information
+ generator.emitLineBreak(stage);
+ generator.emitLine("struct " + uniforms.getName() + "_" +
+ stage.getName(),
+ stage, false);
+ generator.emitScopeBegin(stage);
+ generator.emitLine(uniforms.getName() + " " + structInstanceName + arraySuffix, stage);
+ generator.emitScopeEnd(stage, true);
+}
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/MslResourceBindingContext.h b/source/MaterialXGenMsl/MslResourceBindingContext.h
new file mode 100644
index 0000000000..c7cc6c7a43
--- /dev/null
+++ b/source/MaterialXGenMsl/MslResourceBindingContext.h
@@ -0,0 +1,73 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_MSLRESOURCEBINDING_H
+#define MATERIALX_MSLRESOURCEBINDING_H
+
+/// @file
+/// MSL resource binding context
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Shared pointer to a MslResourceBindingContext
+using MslResourceBindingContextPtr = shared_ptr;
+
+/// @class MslResourceBindingContext
+/// Class representing a resource binding for Msl shader resources.
+class MX_GENMSL_API MslResourceBindingContext : public HwResourceBindingContext
+{
+ public:
+ MslResourceBindingContext(size_t uniformBindingLocation, size_t samplerBindingLocation);
+
+ static MslResourceBindingContextPtr create(
+ size_t uniformBindingLocation=0, size_t samplerBindingLocation=0)
+ {
+ return std::make_shared(
+ uniformBindingLocation, samplerBindingLocation);
+ }
+
+ // Initialize the context before generation starts.
+ void initialize() override;
+
+ // Emit directives for stage
+ void emitDirectives(GenContext& context, ShaderStage& stage) override;
+
+ // Emit uniforms with binding information
+ void emitResourceBindings(GenContext& context, const VariableBlock& uniforms, ShaderStage& stage) override;
+
+ // Emit structured uniforms with binding information and align members where possible
+ void emitStructuredResourceBindings(GenContext& context, const VariableBlock& uniforms,
+ ShaderStage& stage, const std::string& structInstanceName,
+ const std::string& arraySuffix) override;
+
+ // Emit separate binding locations for sampler and uniform table
+ void enableSeparateBindingLocations(bool separateBindingLocation) { _separateBindingLocation = separateBindingLocation; };
+
+ protected:
+ // Binding location for uniform blocks
+ size_t _hwUniformBindLocation = 0;
+
+ // Initial value of uniform binding location
+ size_t _hwInitUniformBindLocation = 0;
+
+ // Binding location for sampler blocks
+ size_t _hwSamplerBindLocation = 0;
+
+ // Initial value of sampler binding location
+ size_t _hwInitSamplerBindLocation = 0;
+
+ // Separate binding locations flag
+ // Indicates whether to use a shared binding counter for samplers and uniforms or separate ones.
+ // By default a shader counter is used.
+ bool _separateBindingLocation = false;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/MslShaderGenerator.cpp b/source/MaterialXGenMsl/MslShaderGenerator.cpp
new file mode 100644
index 0000000000..a5240e095c
--- /dev/null
+++ b/source/MaterialXGenMsl/MslShaderGenerator.cpp
@@ -0,0 +1,1432 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "MslResourceBindingContext.h"
+
+MATERIALX_NAMESPACE_BEGIN
+
+const string MslShaderGenerator::TARGET = "genmsl";
+const string MslShaderGenerator::VERSION = "2.3";
+
+//
+// MslShaderGenerator methods
+//
+
+MslShaderGenerator::MslShaderGenerator() :
+ HwShaderGenerator(MslSyntax::create())
+{
+ //
+ // Register all custom node implementation classes
+ //
+
+ StringVec elementNames;
+
+ //
+ elementNames = {
+ //
+ "IM_switch_float_" + MslShaderGenerator::TARGET,
+ "IM_switch_color3_" + MslShaderGenerator::TARGET,
+ "IM_switch_color4_" + MslShaderGenerator::TARGET,
+ "IM_switch_vector2_" + MslShaderGenerator::TARGET,
+ "IM_switch_vector3_" + MslShaderGenerator::TARGET,
+ "IM_switch_vector4_" + MslShaderGenerator::TARGET,
+
+ //
+ "IM_switch_floatI_" + MslShaderGenerator::TARGET,
+ "IM_switch_color3I_" + MslShaderGenerator::TARGET,
+ "IM_switch_color4I_" + MslShaderGenerator::TARGET,
+ "IM_switch_vector2I_" + MslShaderGenerator::TARGET,
+ "IM_switch_vector3I_" + MslShaderGenerator::TARGET,
+ "IM_switch_vector4I_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, SwitchNode::create);
+
+ //
+ elementNames = {
+ //
+ "IM_swizzle_float_color3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_float_color4_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_float_vector2_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_float_vector3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_float_vector4_" + MslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_color3_float_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color3_color3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color3_color4_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color3_vector2_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color3_vector3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color3_vector4_" + MslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_color4_float_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color4_color3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color4_color4_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color4_vector2_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color4_vector3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_color4_vector4_" + MslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_vector2_float_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_color3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_color4_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_vector2_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_vector3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector2_vector4_" + MslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_vector3_float_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_color3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_color4_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_vector2_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_vector3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector3_vector4_" + MslShaderGenerator::TARGET,
+
+ //
+ "IM_swizzle_vector4_float_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_color3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_color4_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_vector2_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_vector3_" + MslShaderGenerator::TARGET,
+ "IM_swizzle_vector4_vector4_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, SwizzleNode::create);
+
+ //
+ elementNames = {
+ "IM_convert_float_color3_" + MslShaderGenerator::TARGET,
+ "IM_convert_float_color4_" + MslShaderGenerator::TARGET,
+ "IM_convert_float_vector2_" + MslShaderGenerator::TARGET,
+ "IM_convert_float_vector3_" + MslShaderGenerator::TARGET,
+ "IM_convert_float_vector4_" + MslShaderGenerator::TARGET,
+ "IM_convert_vector2_vector3_" + MslShaderGenerator::TARGET,
+ "IM_convert_vector3_vector2_" + MslShaderGenerator::TARGET,
+ "IM_convert_vector3_color3_" + MslShaderGenerator::TARGET,
+ "IM_convert_vector3_vector4_" + MslShaderGenerator::TARGET,
+ "IM_convert_vector4_vector3_" + MslShaderGenerator::TARGET,
+ "IM_convert_vector4_color4_" + MslShaderGenerator::TARGET,
+ "IM_convert_color3_vector3_" + MslShaderGenerator::TARGET,
+ "IM_convert_color4_vector4_" + MslShaderGenerator::TARGET,
+ "IM_convert_color3_color4_" + MslShaderGenerator::TARGET,
+ "IM_convert_color4_color3_" + MslShaderGenerator::TARGET,
+ "IM_convert_boolean_float_" + MslShaderGenerator::TARGET,
+ "IM_convert_integer_float_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, ConvertNode::create);
+
+ //
+ elementNames = {
+ "IM_combine2_vector2_" + MslShaderGenerator::TARGET,
+ "IM_combine2_color4CF_" + MslShaderGenerator::TARGET,
+ "IM_combine2_vector4VF_" + MslShaderGenerator::TARGET,
+ "IM_combine2_vector4VV_" + MslShaderGenerator::TARGET,
+ "IM_combine3_color3_" + MslShaderGenerator::TARGET,
+ "IM_combine3_vector3_" + MslShaderGenerator::TARGET,
+ "IM_combine4_color4_" + MslShaderGenerator::TARGET,
+ "IM_combine4_vector4_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, CombineNode::create);
+
+ //
+ registerImplementation("IM_position_vector3_" + MslShaderGenerator::TARGET, PositionNodeMsl::create);
+ //
+ registerImplementation("IM_normal_vector3_" + MslShaderGenerator::TARGET, NormalNodeMsl::create);
+ //
+ registerImplementation("IM_tangent_vector3_" + MslShaderGenerator::TARGET, TangentNodeMsl::create);
+ //
+ registerImplementation("IM_bitangent_vector3_" + MslShaderGenerator::TARGET, BitangentNodeMsl::create);
+ //
+ registerImplementation("IM_texcoord_vector2_" + MslShaderGenerator::TARGET, TexCoordNodeMsl::create);
+ registerImplementation("IM_texcoord_vector3_" + MslShaderGenerator::TARGET, TexCoordNodeMsl::create);
+ //
+ registerImplementation("IM_geomcolor_float_" + MslShaderGenerator::TARGET, GeomColorNodeMsl::create);
+ registerImplementation("IM_geomcolor_color3_" + MslShaderGenerator::TARGET, GeomColorNodeMsl::create);
+ registerImplementation("IM_geomcolor_color4_" + MslShaderGenerator::TARGET, GeomColorNodeMsl::create);
+ //
+ elementNames = {
+ "IM_geompropvalue_integer_" + MslShaderGenerator::TARGET,
+ "IM_geompropvalue_float_" + MslShaderGenerator::TARGET,
+ "IM_geompropvalue_color3_" + MslShaderGenerator::TARGET,
+ "IM_geompropvalue_color4_" + MslShaderGenerator::TARGET,
+ "IM_geompropvalue_vector2_" + MslShaderGenerator::TARGET,
+ "IM_geompropvalue_vector3_" + MslShaderGenerator::TARGET,
+ "IM_geompropvalue_vector4_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, GeomPropValueNodeMsl::create);
+ registerImplementation("IM_geompropvalue_boolean_" + MslShaderGenerator::TARGET, GeomPropValueNodeMslAsUniform::create);
+ registerImplementation("IM_geompropvalue_string_" + MslShaderGenerator::TARGET, GeomPropValueNodeMslAsUniform::create);
+
+ //
+ registerImplementation("IM_frame_float_" + MslShaderGenerator::TARGET, FrameNodeMsl::create);
+ //
+ registerImplementation("IM_time_float_" + MslShaderGenerator::TARGET, TimeNodeMsl::create);
+
+ //
+ registerImplementation("IM_surface_" + MslShaderGenerator::TARGET, SurfaceNodeMsl::create);
+ registerImplementation("IM_surface_unlit_" + MslShaderGenerator::TARGET, UnlitSurfaceNodeMsl::create);
+
+ //
+ registerImplementation("IM_light_" + MslShaderGenerator::TARGET, LightNodeMsl::create);
+
+ //
+ registerImplementation("IM_point_light_" + MslShaderGenerator::TARGET, LightShaderNodeMsl::create);
+ //
+ registerImplementation("IM_directional_light_" + MslShaderGenerator::TARGET, LightShaderNodeMsl::create);
+ //
+ registerImplementation("IM_spot_light_" + MslShaderGenerator::TARGET, LightShaderNodeMsl::create);
+
+ //
+ registerImplementation("IM_heighttonormal_vector3_" + MslShaderGenerator::TARGET, HeightToNormalNodeMsl::create);
+
+ //
+ elementNames = {
+ "IM_blur_float_" + MslShaderGenerator::TARGET,
+ "IM_blur_color3_" + MslShaderGenerator::TARGET,
+ "IM_blur_color4_" + MslShaderGenerator::TARGET,
+ "IM_blur_vector2_" + MslShaderGenerator::TARGET,
+ "IM_blur_vector3_" + MslShaderGenerator::TARGET,
+ "IM_blur_vector4_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, BlurNodeMsl::create);
+
+ //
+ elementNames = {
+ "IM_image_float_" + MslShaderGenerator::TARGET,
+ "IM_image_color3_" + MslShaderGenerator::TARGET,
+ "IM_image_color4_" + MslShaderGenerator::TARGET,
+ "IM_image_vector2_" + MslShaderGenerator::TARGET,
+ "IM_image_vector3_" + MslShaderGenerator::TARGET,
+ "IM_image_vector4_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, HwImageNode::create);
+
+ //
+ registerImplementation("IM_layer_bsdf_" + MslShaderGenerator::TARGET, ClosureLayerNode::create);
+ registerImplementation("IM_layer_vdf_" + MslShaderGenerator::TARGET, ClosureLayerNode::create);
+ //
+ registerImplementation("IM_mix_bsdf_" + MslShaderGenerator::TARGET, ClosureMixNode::create);
+ registerImplementation("IM_mix_edf_" + MslShaderGenerator::TARGET, ClosureMixNode::create);
+ //
+ registerImplementation("IM_add_bsdf_" + MslShaderGenerator::TARGET, ClosureAddNode::create);
+ registerImplementation("IM_add_edf_" + MslShaderGenerator::TARGET, ClosureAddNode::create);
+ //
+ elementNames = {
+ "IM_multiply_bsdfC_" + MslShaderGenerator::TARGET,
+ "IM_multiply_bsdfF_" + MslShaderGenerator::TARGET,
+ "IM_multiply_edfC_" + MslShaderGenerator::TARGET,
+ "IM_multiply_edfF_" + MslShaderGenerator::TARGET,
+ };
+ registerImplementation(elementNames, ClosureMultiplyNode::create);
+
+ //
+ registerImplementation("IM_thin_film_bsdf_" + MslShaderGenerator::TARGET, NopNode::create);
+
+ //
+ registerImplementation("IM_surfacematerial_" + MslShaderGenerator::TARGET, MaterialNode::create);
+
+ _lightSamplingNodes.push_back(ShaderNode::create(nullptr, "numActiveLightSources", NumLightsNodeMsl::create()));
+ _lightSamplingNodes.push_back(ShaderNode::create(nullptr, "sampleLightSource", LightSamplerNodeMsl::create()));
+}
+
+ShaderPtr MslShaderGenerator::generate(const string& name, ElementPtr element, GenContext& context) const
+{
+ ShaderPtr shader = createShader(name, element, context);
+
+ // Turn on fixed float formatting to make sure float values are
+ // emitted with a decimal point and not as integers, and to avoid
+ // any scientific notation which isn't supported by all OpenGL targets.
+ ScopedFloatFormatting fmt(Value::FloatFormatFixed);
+
+ // Make sure we initialize/reset the binding context before generation.
+ HwResourceBindingContextPtr resourceBindingCtx = getResourceBindingContext(context);
+ if (!resourceBindingCtx)
+ {
+ context.pushUserData(HW::USER_DATA_BINDING_CONTEXT, MslResourceBindingContext::create());
+ resourceBindingCtx = context.getUserData(HW::USER_DATA_BINDING_CONTEXT);
+ }
+
+ // Make sure we initialize/reset the binding context before generation.
+ if (resourceBindingCtx)
+ {
+ resourceBindingCtx->initialize();
+ }
+
+ // Emit code for vertex shader stage
+ ShaderStage& vs = shader->getStage(Stage::VERTEX);
+ emitVertexStage(shader->getGraph(), context, vs);
+ replaceTokens(_tokenSubstitutions, vs);
+
+ // Emit code for pixel shader stage
+ ShaderStage& ps = shader->getStage(Stage::PIXEL);
+ emitPixelStage(shader->getGraph(), context, ps);
+ replaceTokens(_tokenSubstitutions, ps);
+
+ MetalizeGeneratedShader(ps);
+
+ return shader;
+}
+
+void MslShaderGenerator::MetalizeGeneratedShader(ShaderStage& shaderStage) const
+{
+ std::string sourceCode = shaderStage.getSourceCode();
+
+ // Used to convert shared code between GLSL pass by reference parameters to MSL pass by reference.
+ // Converts "inout/out Type variableName" to "thread Type& variableName"
+ size_t pos = 0;
+ {
+ std::array refKeywords = { "out", "inout" };
+ for(const auto& keyword : refKeywords)
+ {
+ pos = sourceCode.find(keyword);
+ while(pos != std::string::npos)
+ {
+ char preceeding = sourceCode[pos - 1], succeeding = sourceCode[pos+keyword.length()];
+ bool isOutKeyword =
+ (preceeding == '(' || preceeding == ',' || std::isspace(preceeding)) &&
+ std::isspace(succeeding) &&
+ succeeding != '\n';
+ size_t beg = pos;
+ pos += keyword.length();
+ if(isOutKeyword)
+ {
+ while(std::isspace(sourceCode[pos])) { ++pos; }
+ size_t typename_beg = pos;
+ while(!std::isspace(sourceCode[pos])) { ++pos; }
+ size_t typename_end = pos;
+ std::string typeName = sourceCode.substr(typename_beg, typename_end - typename_beg);
+ sourceCode.replace(beg, typename_end - beg, "thread " + typeName + "&");
+ }
+ pos = sourceCode.find(keyword, pos);
+ }
+ }
+ }
+
+ // Renames GLSL constructs that are used in shared code to MSL equivalent constructs.
+ std::unordered_map replaceTokens;
+ replaceTokens["sampler2D"] = "MetalTexture";
+ replaceTokens["dFdy"] = "dfdy";
+ replaceTokens["dFdx"] = "dfdx";
+
+ auto isAllowedAfterToken = [](char ch) -> bool
+ {
+ return std::isspace(ch) || ch == '(' || ch == ')' || ch == ',';
+ };
+
+ auto isAllowedBeforeToken = [](char ch) -> bool
+ {
+ return std::isspace(ch) || ch == '(' || ch == ',';
+ };
+
+ for(const auto& t : replaceTokens)
+ {
+ pos = sourceCode.find(t.first);
+ while(pos != std::string::npos)
+ {
+ bool isOutKeyword = isAllowedBeforeToken(sourceCode[pos-1]);
+ size_t beg = pos;
+ pos += t.first.length();
+ isOutKeyword &= isAllowedAfterToken(sourceCode[pos]);
+
+ if(isOutKeyword)
+ {
+ sourceCode.replace(beg, t.first.length(), t.second);
+ pos = sourceCode.find(t.first, beg + t.second.length());
+ }
+ else
+ {
+ pos = sourceCode.find(t.first, beg + t.first.length());
+ }
+ }
+ }
+
+ shaderStage.setSourceCode(sourceCode);
+}
+
+void MslShaderGenerator::emitGlobalVariables(GenContext& context,
+ ShaderStage& stage,
+ EmitGlobalScopeContext situation,
+ bool isVertexShader, bool needsLightData) const
+{
+ int tex_slot = 0;
+ int buffer_slot = isVertexShader ? std::max(static_cast(stage.getInputBlock(HW::VERTEX_INPUTS).size()), 0) : 0;
+
+ bool entryFunctionArgs =
+ situation == EMIT_GLOBAL_SCOPE_CONTEXT_ENTRY_FUNCTION_RESOURCES;
+ bool globalContextInit =
+ situation == EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_INIT;
+ bool globalContextMembers =
+ situation == EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_DECL;
+ bool globalContextConstructorParams =
+ situation == EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_ARGS;
+ bool globalContextConstructorInit =
+ situation == EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_INIT;
+
+ std::string separator = "";
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ if(globalContextMembers)
+ {
+ emitLine("vec4 gl_FragCoord", stage);
+ }
+ if(globalContextConstructorInit)
+ {
+ emitString("gl_FragCoord(", stage);
+ emitLine(stage.getInputBlock(HW::VERTEX_DATA).getInstance() + ".pos)", stage, false);
+ separator = ",";
+ }
+ }
+
+ {
+ auto vertex_inputs = isVertexShader ? stage.getInputBlock(HW::VERTEX_INPUTS)
+ : stage.getInputBlock(HW::VERTEX_DATA);
+ if(!entryFunctionArgs)
+ {
+ if(isVertexShader)
+ {
+
+ for(const auto& it : vertex_inputs.getVariableOrder())
+ {
+ emitString(separator, stage);
+ if(globalContextInit)
+ {
+ emitString(vertex_inputs.getInstance() + "." + it->getName(), stage);
+ }
+ else if (globalContextMembers || globalContextConstructorParams)
+ {
+ emitLine(_syntax->getTypeName(it->getType()) + " " + it->getName(), stage, !globalContextConstructorParams);
+ }
+ else if (globalContextConstructorInit)
+ {
+ emitLine(it->getName() + "(" + it->getName() + ")", stage, false);
+ }
+
+ if(globalContextInit || globalContextConstructorParams || globalContextConstructorInit)
+ separator = ", ";
+ else if(globalContextMembers)
+ separator = "\n";
+ }
+ }
+ else
+ {
+ emitString(separator, stage);
+ if(globalContextInit)
+ {
+ emitString(vertex_inputs.getInstance(), stage);
+ separator = ", ";
+ }
+ else if (globalContextMembers || globalContextConstructorParams)
+ {
+ emitLine(vertex_inputs.getName() + " " + vertex_inputs.getInstance(), stage, !globalContextConstructorParams);
+ if(globalContextConstructorParams)
+ separator = ", ";
+ else
+ separator = "\n";
+ }
+ else if (globalContextConstructorInit)
+ {
+ emitLine(vertex_inputs.getInstance() + "(" + vertex_inputs.getInstance() + ")", stage, false);
+ separator = ", ";
+ }
+ }
+ }
+ else
+ {
+ emitString(vertex_inputs.getName() + " " + vertex_inputs.getInstance() + " [[ stage_in ]]", stage);
+ separator = ", ";
+ }
+ }
+
+ // Add all uniforms
+ for (const auto& it : stage.getUniformBlocks())
+ {
+ const VariableBlock& uniforms = *it.second;
+ bool isLightData = uniforms.getName() == HW::LIGHT_DATA;
+
+ if(!needsLightData && isLightData) continue;
+
+ if(isLightData)
+ {
+ emitString(separator, stage);
+ if(entryFunctionArgs)
+ {
+ emitString(_syntax->getUniformQualifier() + " " +
+ uniforms.getName() + "_" + stage.getName() + "& " +
+ uniforms.getInstance() +
+ "[[ buffer(" + std::to_string(buffer_slot++) + ") ]]",
+ stage);
+ }
+ else if(globalContextInit)
+ {
+ emitLine(uniforms.getInstance() + "." + uniforms.getInstance(), stage, false);
+ }
+ else if(globalContextMembers || globalContextConstructorParams)
+ {
+ const string structArraySuffix = "[" + HW::LIGHT_DATA_MAX_LIGHT_SOURCES + "]";
+ emitLine((globalContextConstructorParams ? (_syntax->getUniformQualifier() + " ") : std::string()) +
+ uniforms.getName() + " " +
+ uniforms.getInstance() +
+ structArraySuffix, stage, !globalContextConstructorParams);
+ }
+ else if(globalContextConstructorInit)
+ {
+ const unsigned int maxLights = std::max(1u, context.getOptions().hwMaxActiveLightSources);
+ emitLine(uniforms.getInstance(), stage, false);
+ emitScopeBegin(stage);
+ for(unsigned int l = 0; l < maxLights; ++l)
+ {
+ emitString(l == 0 ? "" : ", ", stage);
+ emitLine(uniforms.getInstance() +
+ "["+ std::to_string(l) +"]",
+ stage, false);
+ }
+ emitScopeEnd(stage);
+ }
+ }
+ else
+ {
+ if(!entryFunctionArgs)
+ {
+ if (!uniforms.empty())
+ {
+ for (size_t i=0; igetType() != Type::FILENAME)
+ {
+ emitLineBegin(stage);
+ emitString(separator, stage);
+ if(globalContextInit)
+ {
+ emitString(uniforms.getInstance() + "." + uniforms[i]->getVariable(), stage);
+ }
+ else if (globalContextMembers || globalContextConstructorParams)
+ {
+ emitLine(_syntax->getTypeName(uniforms[i]->getType()) + " " + uniforms[i]->getVariable(), stage, !globalContextConstructorParams);
+ }
+ else if (globalContextConstructorInit)
+ {
+ emitLine(uniforms[i]->getVariable() + "(" + uniforms[i]->getVariable() + ")", stage, false);
+ }
+ emitLineEnd(stage, false);
+ }
+ else
+ {
+ if(globalContextInit)
+ {
+ emitString(separator, stage);
+ emitString("MetalTexture", stage);
+ emitScopeBegin(stage);
+ emitString(TEXTURE_NAME(uniforms[i]->getVariable()), stage);
+ emitString(separator, stage);
+ emitString(SAMPLER_NAME(uniforms[i]->getVariable()), stage);
+ emitScopeEnd(stage);
+ }
+ else if (globalContextMembers || globalContextConstructorParams)
+ {
+ emitString(separator, stage);
+ emitVariableDeclaration(uniforms[i], EMPTY_STRING, context, stage, false);
+ emitString(globalContextConstructorParams ? "" : ";", stage);
+ }
+ else if (globalContextConstructorInit)
+ {
+ emitString(separator, stage);
+ emitLine(uniforms[i]->getVariable() + "(" + uniforms[i]->getVariable() + ")", stage, false);
+ }
+ }
+
+ if(globalContextInit || globalContextConstructorParams || globalContextConstructorInit)
+ separator = ", ";
+ else if(globalContextMembers)
+ separator = "\n";
+ }
+ }
+ }
+ else
+ {
+ if (!uniforms.empty())
+ {
+ bool hasUniforms = false;
+ for (const ShaderPort* uniform : uniforms.getVariableOrder())
+ {
+ if (uniform->getType() == Type::FILENAME)
+ {
+ emitString(separator, stage);
+ emitString("texture2d " + TEXTURE_NAME(uniform->getVariable()), stage);
+ emitString(" [[texture(" + std::to_string(tex_slot) + ")]], ", stage);
+ emitString("sampler " + SAMPLER_NAME(uniform->getVariable()), stage);
+ emitString(" [[sampler(" + std::to_string(tex_slot++) + ")]]", stage);
+ emitLineEnd(stage, false);
+ }
+ else
+ {
+ hasUniforms = true;
+ }
+ }
+
+ if(hasUniforms)
+ {
+ emitString(separator, stage);
+ emitString(_syntax->getUniformQualifier() + " " +
+ uniforms.getName() + "& " +
+ uniforms.getInstance() +
+ "[[ buffer(" + std::to_string(buffer_slot++) + ") ]]",
+ stage);
+ }
+ }
+ }
+ }
+
+ if(globalContextInit || entryFunctionArgs || globalContextConstructorParams || globalContextConstructorInit)
+ separator = ", ";
+ else
+ separator = "\n";
+
+ }
+
+ if(!isVertexShader)
+ {
+ const VariableBlock& outputs = stage.getOutputBlock(HW::PIXEL_OUTPUTS);
+ for(auto& it : outputs.getVariableOrder())
+ {
+ if(globalContextMembers)
+ {
+ emitLine(_syntax->getTypeName(it->getType()) + " " + it->getVariable(), stage, true);
+ }
+ }
+ }
+};
+
+void MslShaderGenerator::emitVertexStage(const ShaderGraph& graph, GenContext& context, ShaderStage& stage) const
+{
+ HwResourceBindingContextPtr resourceBindingCtx = getResourceBindingContext(context);
+
+ emitDirectives(context, stage);
+ if (resourceBindingCtx)
+ {
+ resourceBindingCtx->emitDirectives(context, stage);
+ }
+ emitLineBreak(stage);
+
+ emitConstantBufferDeclarations(context, resourceBindingCtx, stage);
+
+ // Add vertex inputs
+ emitInputs(context, stage);
+
+ // Add vertex data outputs block
+ emitOutputs(context, stage);
+
+ emitLine("struct GlobalContext", stage, false);
+ emitScopeBegin(stage);
+ {
+ emitLine("GlobalContext(", stage, false);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_ARGS, true, false);
+ emitLine(") : ", stage, false);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_INIT, true, false);
+ emitLine("{}", stage, false);
+
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_DECL, true, false);
+
+ emitFunctionDefinitions(graph, context, stage);
+
+ const VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ emitLine(vertexData.getName() + " VertexMain()", stage, false);
+ emitScopeBegin(stage);
+ {
+ emitLine(vertexData.getName() + " " + vertexData.getInstance(), stage, true);
+ emitLine("float4 hPositionWorld = " + HW::T_WORLD_MATRIX + " * float4(" + HW::T_IN_POSITION + ", 1.0)", stage);
+ emitLine(vertexData.getInstance() + ".pos" + " = " + HW::T_VIEW_PROJECTION_MATRIX + " * hPositionWorld", stage);
+ emitFunctionCalls(graph, context, stage);
+ emitLineBreak(stage);
+ emitLine("return " + vertexData.getInstance(), stage, true);
+ // For vertex stage just emit all function calls in order
+ // and ignore conditional scope.
+ for (const ShaderNode* node : graph.getNodes())
+ {
+ emitFunctionCall(*node, context, stage, false);
+ }
+
+ emitFunctionBodyEnd(graph, context, stage);
+ }
+ }
+ emitScopeEnd(stage, true, true);
+
+ // Add main function
+ setFunctionName("VertexMain", stage);
+ const VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ emitLine("vertex " + vertexData.getName() + " VertexMain(", stage, false);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_ENTRY_FUNCTION_RESOURCES, true, false);
+ emitLine(")", stage, false);
+ emitScopeBegin(stage);
+ {
+ emitString("\tGlobalContext ctx {", stage);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_INIT, true, false);
+ emitLine("}", stage, true);
+ emitLine(vertexData.getName() + " out = ctx.VertexMain()", stage, true);
+ emitLine("out.pos.y = -out.pos.y", stage, true);
+ emitLine("return out", stage, true);
+ }
+ emitScopeEnd(stage);
+ emitLineBreak(stage);
+
+}
+
+void MslShaderGenerator::emitSpecularEnvironment(GenContext& context, ShaderStage& stage) const
+{
+ int specularMethod = context.getOptions().hwSpecularEnvironmentMethod;
+ if (specularMethod == SPECULAR_ENVIRONMENT_FIS)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_environment_fis.glsl", context, stage);
+ }
+ else if (specularMethod == SPECULAR_ENVIRONMENT_PREFILTER)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_environment_prefilter.glsl", context, stage);
+ }
+ else if (specularMethod == SPECULAR_ENVIRONMENT_NONE)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_environment_none.glsl", context, stage);
+ }
+ else
+ {
+ throw ExceptionShaderGenError("Invalid hardware specular environment method specified: '" + std::to_string(specularMethod) + "'");
+ }
+ emitLineBreak(stage);
+}
+
+void MslShaderGenerator::emitTransmissionRender(GenContext& context, ShaderStage& stage) const
+{
+ int transmissionMethod = context.getOptions().hwTransmissionRenderMethod;
+ if (transmissionMethod == TRANSMISSION_REFRACTION)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_transmission_refract.glsl", context, stage);
+ }
+ else if (transmissionMethod == TRANSMISSION_OPACITY)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_transmission_opacity.glsl", context, stage);
+ }
+ else
+ {
+ throw ExceptionShaderGenError("Invalid transmission render specified: '" + std::to_string(transmissionMethod) + "'");
+ }
+ emitLineBreak(stage);
+}
+
+void MslShaderGenerator::emitDirectives(GenContext&, ShaderStage& stage) const
+{
+ // Add directives
+ emitLine("//Metal Shading Language version " + getVersion(), stage, false);
+ emitLine("#define __METAL__ ", stage, false);
+ emitLine("#include ", stage, false);
+ emitLine("#include ", stage, false);
+ emitLine("using namespace metal;", stage, false);
+
+ emitLine("#define vec2 float2", stage, false);
+ emitLine("#define vec3 float3", stage, false);
+ emitLine("#define vec4 float4", stage, false);
+ emitLine("#define ivec2 int2", stage, false);
+ emitLine("#define ivec3 int3", stage, false);
+ emitLine("#define ivec4 int4", stage, false);
+ emitLine("#define uvec2 uint2", stage, false);
+ emitLine("#define uvec3 uint3", stage, false);
+ emitLine("#define uvec4 uint4", stage, false);
+ emitLine("#define bvec2 bool2", stage, false);
+ emitLine("#define bvec3 bool3", stage, false);
+ emitLine("#define bvec4 bool4", stage, false);
+ emitLine("#define mat3 float3x3",stage, false);
+ emitLine("#define mat4 float4x4",stage, false);
+
+ emitLineBreak(stage);
+}
+
+void MslShaderGenerator::emitConstants(GenContext& context, ShaderStage& stage) const
+{
+ const VariableBlock& constants = stage.getConstantBlock();
+ if (!constants.empty())
+ {
+ emitVariableDeclarations(constants, _syntax->getUniformQualifier(), Syntax::SEMICOLON, context, stage);
+ emitLineBreak(stage);
+ }
+}
+
+void MslShaderGenerator::emitConstantBufferDeclarations(GenContext& context,
+ HwResourceBindingContextPtr resourceBindingCtx,
+ ShaderStage& stage) const
+{
+ // Add all uniforms
+ for (const auto& it : stage.getUniformBlocks())
+ {
+ const VariableBlock& uniforms = *it.second;
+ if (!uniforms.empty())
+ {
+ if(uniforms.getName() == HW::LIGHT_DATA)
+ continue;
+
+ emitComment("Uniform block: " + uniforms.getName(), stage);
+ if (resourceBindingCtx)
+ {
+ resourceBindingCtx->emitResourceBindings(context, uniforms, stage);
+ }
+ else
+ {
+ emitVariableDeclarations(uniforms, _syntax->getUniformQualifier(), Syntax::SEMICOLON, context, stage);
+ emitLineBreak(stage);
+ }
+ }
+ }
+}
+
+void MslShaderGenerator::emitMetalTextureClass(GenContext& context, ShaderStage& stage) const
+{
+ emitLibraryInclude("stdlib/genmsl/lib/mx_texture.metal", context, stage);
+}
+
+void MslShaderGenerator::emitLightData(GenContext& context, ShaderStage& stage) const
+{
+ const VariableBlock& lightData = stage.getUniformBlock(HW::LIGHT_DATA);
+ const string structArraySuffix = "[" + HW::LIGHT_DATA_MAX_LIGHT_SOURCES + "]";
+ const string structName = lightData.getInstance();
+ HwResourceBindingContextPtr resourceBindingCtx = getResourceBindingContext(context);
+ if (resourceBindingCtx)
+ {
+ resourceBindingCtx->emitStructuredResourceBindings(
+ context, lightData, stage, structName, structArraySuffix);
+ }
+ else
+ {
+ emitLine("struct " + lightData.getName(), stage, false);
+ emitScopeBegin(stage);
+ emitVariableDeclarations(lightData, EMPTY_STRING, Syntax::SEMICOLON, context, stage, false);
+ emitScopeEnd(stage, true);
+ emitLineBreak(stage);
+ emitLine("uniform " + lightData.getName() + " " + structName + structArraySuffix, stage);
+ }
+ emitLineBreak(stage);
+}
+
+void MslShaderGenerator::emitInputs(GenContext& context, ShaderStage& stage, const VariableBlock& inputs) const
+{
+ emitComment("Inputs block: " + inputs.getName(), stage);
+ emitLine("struct " + inputs.getName(), stage, false);
+ emitScopeBegin(stage);
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ emitLine("float4 pos [[position]]", stage);
+ }
+
+ for (size_t i=0; igetType());
+ line += " " + inputs[i]->getName() + " ";
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ line += "[[attribute(";
+ line += std::to_string(i);
+ line += ")]]";
+ };
+
+ emitLine(line, stage, true);
+ }
+
+
+ emitScopeEnd(stage, true, false);
+ emitLineBreak(stage);
+}
+
+void MslShaderGenerator::emitInputs(GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ const VariableBlock& vertexInputs = stage.getInputBlock(HW::VERTEX_INPUTS);
+ emitInputs(context, stage, vertexInputs);
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ emitInputs(context, stage, vertexData);
+ }
+}
+
+void MslShaderGenerator::emitOutputs(GenContext& context, ShaderStage& stage) const
+{
+ // Add vertex inputs
+ auto emitOutputsOfShaderSource = [&](const VariableBlock& outputs)
+ {
+ if (!outputs.empty())
+ {
+ emitLine("struct " + outputs.getName(), stage, false);
+ emitScopeBegin(stage);
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ emitLine("float4 pos [[position]]", stage, true);
+ }
+ emitVariableDeclarations(outputs, EMPTY_STRING, Syntax::SEMICOLON, context, stage, false);
+ emitScopeEnd(stage, true, false);
+ emitLineBreak(stage);
+ emitLineBreak(stage);
+ }
+ else
+ {
+ emitLine("struct VertexData", stage, false);
+ emitScopeBegin(stage);
+ emitLine("float4 pos [[position]]", stage, true);
+ emitScopeEnd(stage, true, false);
+ emitLineBreak(stage);
+ emitLineBreak(stage);
+ }
+ };
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ const VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ emitOutputsOfShaderSource(vertexData);
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ emitComment("Pixel shader outputs", stage);
+ const VariableBlock& outputs = stage.getOutputBlock(HW::PIXEL_OUTPUTS);
+ emitOutputsOfShaderSource(outputs);
+ }
+}
+
+HwResourceBindingContextPtr MslShaderGenerator::getResourceBindingContext(GenContext& context) const
+{
+ return context.getUserData(HW::USER_DATA_BINDING_CONTEXT);
+}
+
+string MslShaderGenerator::getVertexDataPrefix(const VariableBlock& vertexData) const
+{
+ return vertexData.getInstance() + ".";
+}
+
+bool MslShaderGenerator::requiresLighting(const ShaderGraph& graph) const
+{
+ const bool isBsdf = graph.hasClassification(ShaderNode::Classification::BSDF);
+ const bool isLitSurfaceShader = graph.hasClassification(ShaderNode::Classification::SHADER) &&
+ graph.hasClassification(ShaderNode::Classification::SURFACE) &&
+ !graph.hasClassification(ShaderNode::Classification::UNLIT);
+ return isBsdf || isLitSurfaceShader;
+}
+
+void MslShaderGenerator::emitMathMatrixScalarMathOperators(GenContext& context, ShaderStage& stage) const
+{
+ emitLibraryInclude("stdlib/genmsl/lib/mx_matscalaroperators.metal", context, stage);
+}
+
+void MslShaderGenerator::emitPixelStage(const ShaderGraph& graph, GenContext& context, ShaderStage& stage) const
+{
+ HwResourceBindingContextPtr resourceBindingCtx = getResourceBindingContext(context);
+
+ // Add directives
+ emitDirectives(context, stage);
+ if (resourceBindingCtx)
+ {
+ resourceBindingCtx->emitDirectives(context, stage);
+ }
+ emitLineBreak(stage);
+
+ emitMetalTextureClass(context, stage);
+
+ // Add type definitions
+ emitTypeDefinitions(context, stage);
+
+ emitConstantBufferDeclarations(context, resourceBindingCtx, stage);
+
+ // Add all constants
+ emitConstants(context, stage);
+
+ // Add vertex data inputs block
+ emitInputs(context, stage);
+
+ // Add the pixel shader output. This needs to be a float4 for rendering
+ // and upstream connection will be converted to float4 if needed in emitFinalOutput()
+ emitOutputs(context, stage);
+
+ // Determine whether lighting is required
+ bool lighting = requiresLighting(graph);
+
+ // Define directional albedo approach
+ if (lighting || context.getOptions().hwWriteAlbedoTable)
+ {
+ emitLine("#define DIRECTIONAL_ALBEDO_METHOD " + std::to_string(int(context.getOptions().hwDirectionalAlbedoMethod)), stage, false);
+ emitLineBreak(stage);
+ }
+
+ // Add lighting support
+ if (lighting)
+ {
+ if (context.getOptions().hwMaxActiveLightSources > 0)
+ {
+ const unsigned int maxLights = std::max(1u, context.getOptions().hwMaxActiveLightSources);
+ emitLine("#define " + HW::LIGHT_DATA_MAX_LIGHT_SOURCES + " " + std::to_string(maxLights), stage, false);
+ }
+
+ if (context.getOptions().hwMaxActiveLightSources > 0)
+ {
+ emitLightData(context, stage);
+ }
+ }
+
+ emitMathMatrixScalarMathOperators(context, stage);
+ emitLine("struct GlobalContext", stage, false);
+ emitScopeBegin(stage);
+ {
+ emitLine("GlobalContext(", stage, false);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_ARGS, false, lighting);
+ emitLine(") : ", stage, false);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_INIT, false, lighting);
+ emitLine("{}", stage, false);
+
+ // Add common math functions
+ emitLine("#define __DECL_GL_MATH_FUNCTIONS__", stage, false);
+ emitLibraryInclude("stdlib/genmsl/lib/mx_math.metal", context, stage);
+ emitLineBreak(stage);
+
+ if(lighting)
+ {
+ emitSpecularEnvironment(context, stage);
+ emitTransmissionRender(context, stage);
+ }
+
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_DECL, false, lighting);
+
+ // Add shadowing support
+ bool shadowing = (lighting && context.getOptions().hwShadowMap) ||
+ context.getOptions().hwWriteDepthMoments;
+ if (shadowing)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_shadow.glsl", context, stage);
+ }
+
+ // Emit directional albedo table code.
+ if (context.getOptions().hwWriteAlbedoTable)
+ {
+ emitLibraryInclude("pbrlib/genglsl/lib/mx_table.glsl", context, stage);
+ emitLineBreak(stage);
+ }
+
+ // Set the include file to use for uv transformations,
+ // depending on the vertical flip flag.
+ if (context.getOptions().fileTextureVerticalFlip)
+ {
+ _tokenSubstitutions[ShaderGenerator::T_FILE_TRANSFORM_UV] = "mx_transform_uv_vflip.glsl";
+ }
+ else
+ {
+ _tokenSubstitutions[ShaderGenerator::T_FILE_TRANSFORM_UV] = "mx_transform_uv.glsl";
+ }
+
+ // Emit uv transform code globally if needed.
+ if (context.getOptions().hwAmbientOcclusion)
+ {
+ emitLibraryInclude("stdlib/genglsl/lib/" + _tokenSubstitutions[ShaderGenerator::T_FILE_TRANSFORM_UV], context, stage);
+ }
+
+ emitLightFunctionDefinitions(graph, context, stage);
+
+ // Emit function definitions for all nodes in the graph.
+ emitFunctionDefinitions(graph, context, stage);
+
+ const ShaderGraphOutputSocket* outputSocket = graph.getOutputSocket();
+
+ // Add main function
+ const VariableBlock& outputs = stage.getOutputBlock(HW::PIXEL_OUTPUTS);
+ emitLine(outputs.getName() + " FragmentMain()", stage, false);
+ emitFunctionBodyBegin(graph, context, stage);
+
+ if (graph.hasClassification(ShaderNode::Classification::CLOSURE) &&
+ !graph.hasClassification(ShaderNode::Classification::SHADER))
+ {
+ // Handle the case where the graph is a direct closure.
+ // We don't support rendering closures without attaching
+ // to a surface shader, so just output black.
+ emitLine(outputSocket->getVariable() + " = float4(0.0, 0.0, 0.0, 1.0)", stage);
+ }
+ else if (context.getOptions().hwWriteDepthMoments)
+ {
+ emitLine(outputSocket->getVariable() + " = float4(mx_compute_depth_moments(), 0.0, 1.0)", stage);
+ }
+ else if (context.getOptions().hwWriteAlbedoTable)
+ {
+ emitLine(outputSocket->getVariable() + " = float4(mx_generate_dir_albedo_table(), 1.0)", stage);
+ }
+ else
+ {
+ // Add all function calls.
+ //
+ // Surface shaders need special handling.
+ if (graph.hasClassification(ShaderNode::Classification::SHADER | ShaderNode::Classification::SURFACE))
+ {
+ // Emit all texturing nodes. These are inputs to any
+ // closure/shader nodes and need to be emitted first.
+ emitFunctionCalls(graph, context, stage, ShaderNode::Classification::TEXTURE);
+
+ // Emit function calls for "root" closure/shader nodes.
+ // These will internally emit function calls for any dependent closure nodes upstream.
+ for (ShaderGraphOutputSocket* socket : graph.getOutputSockets())
+ {
+ if (socket->getConnection())
+ {
+ const ShaderNode* upstream = socket->getConnection()->getNode();
+ if (upstream->getParent() == &graph &&
+ (upstream->hasClassification(ShaderNode::Classification::CLOSURE) ||
+ upstream->hasClassification(ShaderNode::Classification::SHADER)))
+ {
+ emitFunctionCall(*upstream, context, stage);
+ }
+ }
+ }
+ }
+ else
+ {
+ // No surface shader graph so just generate all
+ // function calls in order.
+ emitFunctionCalls(graph, context, stage);
+ }
+
+ // Emit final output
+ const ShaderOutput* outputConnection = outputSocket->getConnection();
+ if (outputConnection)
+ {
+ string finalOutput = outputConnection->getVariable();
+ const string& channels = outputSocket->getChannels();
+ if (!channels.empty())
+ {
+ finalOutput = _syntax->getSwizzledVariable(finalOutput, outputConnection->getType(), channels, outputSocket->getType());
+ }
+
+ if (graph.hasClassification(ShaderNode::Classification::SURFACE))
+ {
+ if (context.getOptions().hwTransparency)
+ {
+ emitLine("float outAlpha = clamp(1.0 - dot(" + finalOutput + ".transparency, float3(0.3333)), 0.0, 1.0)", stage);
+ emitLine(outputSocket->getVariable() + " = float4(" + finalOutput + ".color, outAlpha)", stage);
+ emitLine("if (outAlpha < " + HW::T_ALPHA_THRESHOLD + ")", stage, false);
+ emitScopeBegin(stage);
+ emitLine("discard_fragment()", stage);
+ emitScopeEnd(stage);
+ }
+ else
+ {
+ emitLine(outputSocket->getVariable() + " = float4(" + finalOutput + ".color, 1.0)", stage);
+ }
+ }
+ else
+ {
+ if (!outputSocket->getType()->isFloat4())
+ {
+ toVec4(outputSocket->getType(), finalOutput);
+ }
+ emitLine(outputSocket->getVariable() + " = " + finalOutput, stage);
+ }
+ }
+ else
+ {
+ string outputValue = outputSocket->getValue() ?
+ _syntax->getValue(outputSocket->getType(), *outputSocket->getValue()) :
+ _syntax->getDefaultValue(outputSocket->getType());
+ if (!outputSocket->getType()->isFloat4())
+ {
+ string finalOutput = outputSocket->getVariable() + "_tmp";
+ emitLine(_syntax->getTypeName(outputSocket->getType()) + " " + finalOutput + " = " + outputValue, stage);
+ toVec4(outputSocket->getType(), finalOutput);
+ emitLine(outputSocket->getVariable() + " = " + finalOutput, stage);
+ }
+ else
+ {
+ emitLine(outputSocket->getVariable() + " = " + outputValue, stage);
+ }
+ }
+ }
+
+
+ {
+ std::string separator = "";
+ emitString("return " + outputs.getName() + "{", stage);
+ for(auto it : outputs.getVariableOrder())
+ {
+ emitString(separator + it->getVariable(), stage);
+ separator = ", ";
+ }
+ emitLine("}", stage, true);
+ }
+
+ // End main function
+ emitFunctionBodyEnd(graph, context, stage);
+
+ }
+ emitScopeEnd(stage, true, true);
+
+ // Add main function
+ {
+ setFunctionName("FragmentMain", stage);
+ const VariableBlock& outputs = stage.getOutputBlock(HW::PIXEL_OUTPUTS);
+ emitLine("fragment " + outputs.getName() + " FragmentMain(", stage, false);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_ENTRY_FUNCTION_RESOURCES, false, lighting);
+ emitLine(")", stage, false);
+ emitScopeBegin(stage);
+ {
+ emitString("\tGlobalContext ctx {", stage);
+ emitGlobalVariables(context, stage, EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_INIT, false, lighting);
+ emitLine("}", stage, true);
+ emitLine("return ctx.FragmentMain()", stage, true);
+ }
+ emitScopeEnd(stage);
+ emitLineBreak(stage);
+ }
+}
+
+void MslShaderGenerator::emitLightFunctionDefinitions(const ShaderGraph& graph, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+
+ // Emit Light functions if requested
+ if (requiresLighting(graph) && context.getOptions().hwMaxActiveLightSources > 0)
+ {
+ // For surface shaders we need light shaders
+ if (graph.hasClassification(ShaderNode::Classification::SHADER | ShaderNode::Classification::SURFACE))
+ {
+ // Emit functions for all bound light shaders
+ HwLightShadersPtr lightShaders = context.getUserData(HW::USER_DATA_LIGHT_SHADERS);
+ if (lightShaders)
+ {
+ for (const auto& it : lightShaders->get())
+ {
+ emitFunctionDefinition(*it.second, context, stage);
+ }
+ }
+ // Emit functions for light sampling
+ for (const auto& it : _lightSamplingNodes)
+ {
+ emitFunctionDefinition(*it, context, stage);
+ }
+ }
+ }
+ }
+}
+
+void MslShaderGenerator::toVec4(const TypeDesc* type, string& variable)
+{
+ if (type->isFloat3())
+ {
+ variable = "float4(" + variable + ", 1.0)";
+ }
+ else if (type->isFloat2())
+ {
+ variable = "float4(" + variable + ", 0.0, 1.0)";
+ }
+ else if (type == Type::FLOAT || type == Type::INTEGER)
+ {
+ variable = "float4(" + variable + ", " + variable + ", " + variable + ", 1.0)";
+ }
+ else if (type == Type::BSDF || type == Type::EDF)
+ {
+ variable = "float4(" + variable + ", 1.0)";
+ }
+ else
+ {
+ // Can't understand other types. Just return black.
+ variable = "float4(0.0, 0.0, 0.0, 1.0)";
+ }
+}
+
+void MslShaderGenerator::emitVariableDeclaration(const ShaderPort* variable, const string& qualifier,
+ GenContext&, ShaderStage& stage,
+ bool assignValue) const
+{
+ // A file texture input needs special handling on MSL
+ if (variable->getType() == Type::FILENAME)
+ {
+ // Samplers must always be uniforms
+ string str = qualifier.empty() ? EMPTY_STRING : qualifier + " ";
+ emitString(str + "MetalTexture " + variable->getVariable(), stage);
+ }
+ else
+ {
+ string str = qualifier.empty() ? EMPTY_STRING : qualifier + " ";
+
+ str += _syntax->getTypeName(variable->getType()) + " " + variable->getVariable();
+
+ // If an array we need an array qualifier (suffix) for the variable name
+ if (variable->getType()->isArray() && variable->getValue())
+ {
+ str += _syntax->getArrayVariableSuffix(variable->getType(), *variable->getValue());
+ }
+
+ if (!variable->getSemantic().empty())
+ {
+ str += " : " + variable->getSemantic();
+ }
+
+ // Varying parameters of type int must be flat qualified on output from vertex stage and
+ // input to pixel stage. The only way to get these is with geompropvalue_integer nodes.
+ if (qualifier.empty() && variable->getType() == Type::INTEGER && !assignValue && variable->getName().rfind(HW::T_IN_GEOMPROP, 0) == 0) {
+ str += "[[ " + MslSyntax::FLAT_QUALIFIER + " ]]";
+ }
+
+ if (assignValue)
+ {
+ const string valueStr = (variable->getValue() ?
+ _syntax->getValue(variable->getType(), *variable->getValue(), true) :
+ _syntax->getDefaultValue(variable->getType(), true));
+ str += valueStr.empty() ? EMPTY_STRING : " = " + valueStr;
+ }
+
+ emitString(str, stage);
+ }
+}
+
+ShaderNodeImplPtr MslShaderGenerator::getImplementation(const NodeDef& nodedef, GenContext& context) const
+{
+ InterfaceElementPtr implElement = nodedef.getImplementation(getTarget());
+ if (!implElement)
+ {
+ return nullptr;
+ }
+
+ const string& name = implElement->getName();
+
+ // Check if it's created and cached already.
+ ShaderNodeImplPtr impl = context.findNodeImplementation(name);
+ if (impl)
+ {
+ return impl;
+ }
+
+ vector outputs = nodedef.getActiveOutputs();
+ if (outputs.empty())
+ {
+ throw ExceptionShaderGenError("NodeDef '" + nodedef.getName() + "' has no outputs defined");
+ }
+
+ const TypeDesc* outputType = TypeDesc::get(outputs[0]->getType());
+
+ if (implElement->isA())
+ {
+ // Use a compound implementation.
+ if (outputType == Type::LIGHTSHADER)
+ {
+ impl = LightCompoundNodeMsl::create();
+ }
+ else if (outputType->isClosure())
+ {
+ impl = ClosureCompoundNode::create();
+ }
+ else
+ {
+ impl = CompoundNode::create();
+ }
+ }
+ else if (implElement->isA())
+ {
+ // Try creating a new in the factory.
+ impl = _implFactory.create(name);
+ if (!impl)
+ {
+ // Fall back to source code implementation.
+ if (outputType->isClosure())
+ {
+ impl = ClosureSourceCodeNode::create();
+ }
+ else
+ {
+ impl = SourceCodeNode::create();
+ }
+ }
+ }
+ if (!impl)
+ {
+ return nullptr;
+ }
+
+ impl->initialize(*implElement, context);
+
+ // Cache it.
+ context.addNodeImplementation(name, impl);
+
+ return impl;
+}
+
+
+const string MslImplementation::SPACE = "space";
+const string MslImplementation::TO_SPACE = "tospace";
+const string MslImplementation::FROM_SPACE = "fromspace";
+const string MslImplementation::WORLD = "world";
+const string MslImplementation::OBJECT = "object";
+const string MslImplementation::MODEL = "model";
+const string MslImplementation::INDEX = "index";
+const string MslImplementation::GEOMPROP = "geomprop";
+
+namespace
+{
+ // List name of inputs that are not to be editable and
+ // published as shader uniforms in MSL.
+ const std::set IMMUTABLE_INPUTS =
+ {
+ // Geometric node inputs are immutable since a shader needs regeneration if they change.
+ "index", "space", "attrname"
+ };
+}
+
+const string& MslImplementation::getTarget() const
+{
+ return MslShaderGenerator::TARGET;
+}
+
+bool MslImplementation::isEditable(const ShaderInput& input) const
+{
+ return IMMUTABLE_INPUTS.count(input.getName()) == 0;
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/MslShaderGenerator.h b/source/MaterialXGenMsl/MslShaderGenerator.h
new file mode 100644
index 0000000000..68fe5d6497
--- /dev/null
+++ b/source/MaterialXGenMsl/MslShaderGenerator.h
@@ -0,0 +1,152 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_MSLSHADERGENERATOR_H
+#define MATERIALX_MSLSHADERGENERATOR_H
+
+/// @file
+/// MSL shader generator
+
+#include
+
+#include
+
+#define TEXTURE_NAME(t) ((t) + "_tex")
+#define SAMPLER_NAME(t) ((t) + "_sampler")
+
+MATERIALX_NAMESPACE_BEGIN
+
+using MslShaderGeneratorPtr = shared_ptr;
+
+/// Base class for MSL (OpenGL Shading Language) code generation.
+/// A generator for a specific MSL target should be derived from this class.
+class MX_GENMSL_API MslShaderGenerator : public HwShaderGenerator
+{
+ public:
+ MslShaderGenerator();
+
+ static ShaderGeneratorPtr create() { return std::make_shared(); }
+
+ /// Generate a shader starting from the given element, translating
+ /// the element and all dependencies upstream into shader code.
+ ShaderPtr generate(const string& name, ElementPtr element, GenContext& context) const override;
+
+ /// Return a unique identifier for the target this generator is for
+ const string& getTarget() const override { return TARGET; }
+
+ /// Return the version string for the MSL version this generator is for
+ virtual const string& getVersion() const { return VERSION; }
+
+ /// Emit a shader variable.
+ void emitVariableDeclaration(const ShaderPort* variable, const string& qualifier, GenContext& context, ShaderStage& stage,
+ bool assignValue = true) const override;
+
+ /// Return a registered shader node implementation given an implementation element.
+ /// The element must be an Implementation or a NodeGraph acting as implementation.
+ ShaderNodeImplPtr getImplementation(const NodeDef& nodedef, GenContext& context) const override;
+
+ /// Determine the prefix of vertex data variables.
+ virtual string getVertexDataPrefix(const VariableBlock& vertexData) const;
+
+ public:
+ /// Unique identifier for this generator target
+ static const string TARGET;
+
+ /// Version string for the generator target
+ static const string VERSION;
+
+ protected:
+ virtual void emitVertexStage(const ShaderGraph& graph, GenContext& context, ShaderStage& stage) const;
+ virtual void emitPixelStage(const ShaderGraph& graph, GenContext& context, ShaderStage& stage) const;
+
+ virtual void emitMetalTextureClass(GenContext& context, ShaderStage& stage) const;
+ virtual void emitDirectives(GenContext& context, ShaderStage& stage) const;
+ virtual void emitConstants(GenContext& context, ShaderStage& stage) const;
+ virtual void emitLightData(GenContext& context, ShaderStage& stage) const;
+ virtual void emitInputs(GenContext& context, ShaderStage& stage) const;
+ virtual void emitOutputs(GenContext& context, ShaderStage& stage) const;
+
+ virtual void emitMathMatrixScalarMathOperators(GenContext& context, ShaderStage& stage) const;
+ virtual void MetalizeGeneratedShader(ShaderStage& shaderStage) const;
+
+ void emitConstantBufferDeclarations(GenContext& context,
+ HwResourceBindingContextPtr resourceBindingCtx,
+ ShaderStage& stage) const;
+
+ enum EmitGlobalScopeContext
+ {
+ EMIT_GLOBAL_SCOPE_CONTEXT_ENTRY_FUNCTION_RESOURCES = 0,
+ EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_INIT = 1,
+ EMIT_GLOBAL_SCOPE_CONTEXT_MEMBER_DECL = 2,
+ EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_ARGS = 3,
+ EMIT_GLOBAL_SCOPE_CONTEXT_CONSTRUCTOR_INIT = 4
+ };
+
+ void emitGlobalVariables(GenContext& context, ShaderStage& stage,
+ EmitGlobalScopeContext situation,
+ bool isVertexShader,
+ bool needsLightData) const;
+
+ void emitInputs(GenContext& context, ShaderStage& stage,
+ const VariableBlock& inputs) const;
+
+ virtual HwResourceBindingContextPtr getResourceBindingContext(GenContext& context) const;
+
+ /// Logic to indicate whether code to support direct lighting should be emitted.
+ /// By default if the graph is classified as a shader, or BSDF node then lighting is assumed to be required.
+ /// Derived classes can override this logic.
+ virtual bool requiresLighting(const ShaderGraph& graph) const;
+
+ /// Emit specular environment lookup code
+ virtual void emitSpecularEnvironment(GenContext& context, ShaderStage& stage) const;
+
+ /// Emit transmission rendering code
+ virtual void emitTransmissionRender(GenContext& context, ShaderStage& stage) const;
+
+ /// Emit function definitions for lighting code
+ virtual void emitLightFunctionDefinitions(const ShaderGraph& graph, GenContext& context, ShaderStage& stage) const;
+
+ static void toVec4(const TypeDesc* type, string& variable);
+
+ /// Nodes used internally for light sampling.
+ vector _lightSamplingNodes;
+};
+
+
+/// Base class for common MSL node implementations
+class MX_GENMSL_API MslImplementation : public ShaderNodeImpl
+{
+ public:
+ const string& getTarget() const override;
+
+ bool isEditable(const ShaderInput& input) const override;
+
+ protected:
+ MslImplementation() {}
+
+ // Integer identifiers for coordinate spaces.
+ // The order must match the order given for
+ // the space enum string in stdlib.
+ enum Space
+ {
+ MODEL_SPACE = 0,
+ OBJECT_SPACE = 1,
+ WORLD_SPACE = 2
+ };
+
+ /// Internal string constants
+ static const string SPACE;
+ static const string TO_SPACE;
+ static const string FROM_SPACE;
+ static const string WORLD;
+ static const string OBJECT;
+ static const string MODEL;
+ static const string INDEX;
+ static const string GEOMPROP;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/MslSyntax.cpp b/source/MaterialXGenMsl/MslSyntax.cpp
new file mode 100644
index 0000000000..80bb6803d9
--- /dev/null
+++ b/source/MaterialXGenMsl/MslSyntax.cpp
@@ -0,0 +1,428 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+namespace
+{
+
+// Since MSL doesn't support strings we use integers instead.
+// TODO: Support options strings by converting to a corresponding enum integer
+class MslStringTypeSyntax : public StringTypeSyntax
+{
+ public:
+ MslStringTypeSyntax() : StringTypeSyntax("int", "0", "0") {}
+
+ string getValue(const Value& /*value*/, bool /*uniform*/) const override
+ {
+ return "0";
+ }
+};
+
+class MslArrayTypeSyntax : public ScalarTypeSyntax
+{
+ public:
+ MslArrayTypeSyntax(const string& name) :
+ ScalarTypeSyntax(name, EMPTY_STRING, EMPTY_STRING, EMPTY_STRING)
+ {}
+
+ string getValue(const Value& value, bool /*uniform*/) const override
+ {
+ size_t arraySize = getSize(value);
+ if (arraySize > 0)
+ {
+ return "{" + value.getValueString() + "}";
+ }
+ return EMPTY_STRING;
+ }
+
+ string getValue(const StringVec& values, bool /*uniform*/) const override
+ {
+ if (values.empty())
+ {
+ throw ExceptionShaderGenError("No values given to construct an array value");
+ }
+
+ string result = "{" + values[0];
+ for (size_t i = 1; i valueArray = value.asA>();
+ return valueArray.size();
+ }
+};
+
+class MslIntegerArrayTypeSyntax : public MslArrayTypeSyntax
+{
+ public:
+ explicit MslIntegerArrayTypeSyntax(const string& name) :
+ MslArrayTypeSyntax(name)
+ {}
+
+ protected:
+ size_t getSize(const Value& value) const override
+ {
+ vector valueArray = value.asA>();
+ return valueArray.size();
+ }
+};
+
+} // anonymous namespace
+
+const string MslSyntax::INPUT_QUALIFIER = "in";
+const string MslSyntax::OUTPUT_QUALIFIER = "out";
+const string MslSyntax::UNIFORM_QUALIFIER = "constant";
+const string MslSyntax::CONSTANT_QUALIFIER = "const";
+const string MslSyntax::FLAT_QUALIFIER = "flat";
+const string MslSyntax::SOURCE_FILE_EXTENSION = ".metal";
+const string MslSyntax::STRUCT_KEYWORD = "struct";
+const StringVec MslSyntax::VEC2_MEMBERS = { ".x", ".y" };
+const StringVec MslSyntax::VEC3_MEMBERS = { ".x", ".y", ".z" };
+const StringVec MslSyntax::VEC4_MEMBERS = { ".x", ".y", ".z", ".w" };
+
+//
+// MslSyntax methods
+//
+
+MslSyntax::MslSyntax()
+{
+ // Add in all reserved words and keywords in MSL
+ registerReservedWords(
+ {
+ "centroid", "flat", "smooth", "noperspective", "patch", "sample",
+ "break", "continue", "do", "for", "while", "switch", "case", "default",
+ "if", "else,", "subroutine", "in", "out", "inout",
+ "float", "double", "int", "void", "bool", "true", "false",
+ "invariant", "discard_fragment", "return",
+ "float2x2", "float2x3", "float2x4",
+ "float3x2", "float3x3", "float3x4",
+ "float4x2", "float4x3", "float4x4",
+ "float2", "float3", "float4", "int2", "int3", "int4", "bool2", "bool3", "bool4",
+ "uint", "uint2", "uint3", "uint4",
+ "lowp", "mediump", "highp", "precision",
+ "sampler",
+ "common", "partition", "active", "asm",
+ "struct", "class", "union", "enum", "typedef", "template", "this", "packed",
+ "inline", "noinline", "volatile", "public", "static", "extern", "external", "interface",
+ "long", "short", "half", "fixed", "unsigned", "superp", "input", "output",
+ "half2", "half3", "half4",
+ "sampler3DRect", "filter",
+ "texture1d", "texture2d", "texture3d", "textureCube",
+ "buffer",
+ "sizeof", "cast", "namespace", "using", "row_major",
+ "mix", "sampler"
+ });
+
+ // Register restricted tokens in MSL
+ StringMap tokens;
+ tokens["__"] = "_";
+ tokens["gl_"] = "gll";
+ tokens["webgl_"] = "webgll";
+ tokens["_webgl"] = "wwebgl";
+ registerInvalidTokens(tokens);
+
+ //
+ // Register syntax handlers for each data type.
+ //
+
+ registerTypeSyntax
+ (
+ Type::FLOAT,
+ std::make_shared(
+ "float",
+ "0.0",
+ "0.0")
+ );
+
+ registerTypeSyntax
+ (
+ Type::FLOATARRAY,
+ std::make_shared(
+ "float")
+ );
+
+ registerTypeSyntax
+ (
+ Type::INTEGER,
+ std::make_shared(
+ "int",
+ "0",
+ "0")
+ );
+
+ registerTypeSyntax
+ (
+ Type::INTEGERARRAY,
+ std::make_shared(
+ "int")
+ );
+
+ registerTypeSyntax
+ (
+ Type::BOOLEAN,
+ std::make_shared(
+ "bool",
+ "false",
+ "false")
+ );
+
+ registerTypeSyntax
+ (
+ Type::COLOR3,
+ std::make_shared(
+ "vec3",
+ "vec3(0.0)",
+ "vec3(0.0)",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ VEC3_MEMBERS)
+ );
+
+ registerTypeSyntax
+ (
+ Type::COLOR4,
+ std::make_shared(
+ "vec4",
+ "vec4(0.0)",
+ "vec4(0.0)",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ VEC4_MEMBERS)
+ );
+
+ registerTypeSyntax
+ (
+ Type::VECTOR2,
+ std::make_shared(
+ "vec2",
+ "vec2(0.0)",
+ "vec2(0.0)",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ VEC2_MEMBERS)
+ );
+
+ registerTypeSyntax
+ (
+ Type::VECTOR3,
+ std::make_shared(
+ "vec3",
+ "vec3(0.0)",
+ "vec3(0.0)",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ VEC3_MEMBERS)
+ );
+
+ registerTypeSyntax
+ (
+ Type::VECTOR4,
+ std::make_shared(
+ "vec4",
+ "vec4(0.0)",
+ "vec4(0.0)",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ VEC4_MEMBERS)
+ );
+
+ registerTypeSyntax
+ (
+ Type::MATRIX33,
+ std::make_shared(
+ "mat3",
+ "mat3(1.0)",
+ "mat3(1.0)")
+ );
+
+ registerTypeSyntax
+ (
+ Type::MATRIX44,
+ std::make_shared(
+ "mat4",
+ "mat4(1.0)",
+ "mat4(1.0)")
+ );
+
+ registerTypeSyntax
+ (
+ Type::STRING,
+ std::make_shared()
+ );
+
+ registerTypeSyntax
+ (
+ Type::FILENAME,
+ std::make_shared(
+ "MetalTexture",
+ EMPTY_STRING,
+ EMPTY_STRING)
+ );
+
+ registerTypeSyntax
+ (
+ Type::BSDF,
+ std::make_shared(
+ "BSDF",
+ "BSDF{float3(0.0),float3(1.0), 0.0, 0.0}",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ "struct BSDF { float3 response; float3 throughput; float thickness; float ior; };")
+ );
+
+ registerTypeSyntax
+ (
+ Type::EDF,
+ std::make_shared(
+ "EDF",
+ "EDF(0.0)",
+ "EDF(0.0)",
+ "float3",
+ "#define EDF float3")
+ );
+
+ registerTypeSyntax
+ (
+ Type::VDF,
+ std::make_shared(
+ "BSDF",
+ "BSDF{float3(0.0),float3(1.0), 0.0, 0.0}",
+ EMPTY_STRING)
+ );
+
+ registerTypeSyntax
+ (
+ Type::SURFACESHADER,
+ std::make_shared(
+ "surfaceshader",
+ "surfaceshader{float3(0.0),float3(0.0)}",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ "struct surfaceshader { float3 color; float3 transparency; };")
+ );
+
+ registerTypeSyntax
+ (
+ Type::VOLUMESHADER,
+ std::make_shared(
+ "volumeshader",
+ "volumeshader{float3(0.0),float3(0.0)}",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ "struct volumeshader { float3 color; float3 transparency; };")
+ );
+
+ registerTypeSyntax
+ (
+ Type::DISPLACEMENTSHADER,
+ std::make_shared(
+ "displacementshader",
+ "displacementshader{float3(0.0),1.0}",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ "struct displacementshader { float3 offset; float scale; };")
+ );
+
+ registerTypeSyntax
+ (
+ Type::LIGHTSHADER,
+ std::make_shared(
+ "lightshader",
+ "lightshader{float3(0.0),float3(0.0)}",
+ EMPTY_STRING,
+ EMPTY_STRING,
+ "struct lightshader { float3 intensity; float3 direction; };")
+ );
+
+ registerTypeSyntax
+ (
+ Type::MATERIAL,
+ std::make_shared(
+ "material",
+ "material{float3(0.0),float3(0.0)}",
+ EMPTY_STRING,
+ "surfaceshader",
+ "#define material surfaceshader")
+ );
+}
+
+string MslSyntax::getOutputTypeName(const TypeDesc* type) const
+{
+ const TypeSyntax& syntax = getTypeSyntax(type);
+ return "thread " + syntax.getName() + "&";
+}
+
+bool MslSyntax::typeSupported(const TypeDesc* type) const
+{
+ return type != Type::STRING;
+}
+
+
+bool MslSyntax::remapEnumeration(const string& value, const TypeDesc* type, const string& enumNames, std::pair& result) const
+{
+ // Early out if not an enum input.
+ if (enumNames.empty())
+ {
+ return false;
+ }
+
+ // Don't convert already supported types
+ // or filenames and arrays.
+ if (typeSupported(type) ||
+ type == Type::FILENAME || (type && type->isArray()))
+ {
+ return false;
+ }
+
+ // For MSL we always convert to integer,
+ // with the integer value being an index into the enumeration.
+ result.first = Type::INTEGER;
+ result.second = nullptr;
+
+ // Try remapping to an enum value.
+ if (!value.empty())
+ {
+ StringVec valueElemEnumsVec = splitString(enumNames, ",");
+ for (size_t i=0; i(std::distance(valueElemEnumsVec.begin(), pos));
+ result.second = Value::createValue(index);
+ }
+
+ return true;
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/MslSyntax.h b/source/MaterialXGenMsl/MslSyntax.h
new file mode 100644
index 0000000000..a209bd81ef
--- /dev/null
+++ b/source/MaterialXGenMsl/MslSyntax.h
@@ -0,0 +1,56 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_MSL_SYNTAX_H
+#define MATERIALX_MSL_SYNTAX_H
+
+/// @file
+/// MSL syntax class
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Syntax class for MSL (OpenGL Shading Language)
+class MX_GENMSL_API MslSyntax : public Syntax
+{
+ public:
+ MslSyntax();
+
+ static SyntaxPtr create() { return std::make_shared(); }
+
+ const string& getInputQualifier() const override { return INPUT_QUALIFIER; }
+ const string& getOutputQualifier() const override { return OUTPUT_QUALIFIER; }
+ const string& getConstantQualifier() const override { return CONSTANT_QUALIFIER; };
+ const string& getUniformQualifier() const override { return UNIFORM_QUALIFIER; };
+ const string& getSourceFileExtension() const override { return SOURCE_FILE_EXTENSION; };
+ const string& getStructKeyword() const { return STRUCT_KEYWORD; }
+
+ string getOutputTypeName(const TypeDesc* type) const override;
+
+ bool typeSupported(const TypeDesc* type) const override;
+
+ /// Given an input specification attempt to remap this to an enumeration which is accepted by
+ /// the shader generator. The enumeration may be converted to a different type than the input.
+ bool remapEnumeration(const string& value, const TypeDesc* type, const string& enumNames, std::pair& result) const override;
+
+ static const string INPUT_QUALIFIER;
+ static const string OUTPUT_QUALIFIER;
+ static const string UNIFORM_QUALIFIER;
+ static const string CONSTANT_QUALIFIER;
+ static const string FLAT_QUALIFIER;
+ static const string SOURCE_FILE_EXTENSION;
+ static const string STRUCT_KEYWORD;
+
+ static const StringVec VEC2_MEMBERS;
+ static const StringVec VEC3_MEMBERS;
+ static const StringVec VEC4_MEMBERS;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/BitangentNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/BitangentNodeMsl.cpp
new file mode 100644
index 0000000000..e6cd26a451
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/BitangentNodeMsl.cpp
@@ -0,0 +1,136 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr BitangentNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void BitangentNodeMsl::createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const
+{
+ const GenOptions& options = context.getOptions();
+
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ if (options.hwImplicitBitangents)
+ {
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_NORMAL, vs);
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_TANGENT, vs);
+ }
+ else
+ {
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_BITANGENT, vs);
+ }
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+ if (space == WORLD_SPACE)
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_BITANGENT_WORLD, vs, ps);
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::MATRIX44, HW::T_WORLD_MATRIX, vs);
+
+ if (options.hwImplicitBitangents)
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_NORMAL_WORLD, vs, ps);
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_TANGENT_WORLD, vs, ps);
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::MATRIX44, HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX, vs);
+ }
+ }
+ else
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_BITANGENT_OBJECT, vs, ps);
+ }
+}
+
+
+void BitangentNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ const GenOptions& options = context.getOptions();
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ if (space == WORLD_SPACE)
+ {
+ ShaderPort* bitangent = vertexData[HW::T_BITANGENT_WORLD];
+
+ if (!bitangent->isEmitted())
+ {
+ bitangent->setEmitted();
+
+ if (options.hwImplicitBitangents)
+ {
+ ShaderPort* normal = vertexData[HW::T_NORMAL_WORLD];
+ if (!normal->isEmitted())
+ {
+ normal->setEmitted();
+ shadergen.emitLine(prefix + normal->getVariable() + " = normalize((" + HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX + " * vec4(" + HW::T_IN_NORMAL + ", 0.0)).xyz)", stage);
+ }
+ ShaderPort* tangent = vertexData[HW::T_TANGENT_WORLD];
+ if (!tangent->isEmitted())
+ {
+ tangent->setEmitted();
+ shadergen.emitLine(prefix + tangent->getVariable() + " = normalize((" + HW::T_WORLD_MATRIX + " * vec4(" + HW::T_IN_TANGENT + ", 0.0)).xyz)", stage);
+ }
+ shadergen.emitLine(prefix + bitangent->getVariable() + " = cross(" + prefix + normal->getVariable() + ", " + prefix + tangent->getVariable() + ")", stage);
+ }
+ else
+ {
+ shadergen.emitLine(prefix + bitangent->getVariable() + " = normalize((" + HW::T_WORLD_MATRIX + " * vec4(" + HW::T_IN_BITANGENT + ", 0.0)).xyz)", stage);
+ }
+ }
+ }
+ else
+ {
+ ShaderPort* bitangent = vertexData[HW::T_BITANGENT_OBJECT];
+ if (!bitangent->isEmitted())
+ {
+ bitangent->setEmitted();
+
+ if (options.hwImplicitBitangents)
+ {
+ shadergen.emitLine(prefix + bitangent->getVariable() + " = cross(" + HW::T_IN_NORMAL + ", " + HW::T_IN_TANGENT + ")", stage);
+ }
+ else
+ {
+ shadergen.emitLine(prefix + bitangent->getVariable() + " = " + HW::T_IN_BITANGENT, stage);
+ }
+ }
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ if (space == WORLD_SPACE)
+ {
+ const ShaderPort* bitangent = vertexData[HW::T_BITANGENT_WORLD];
+ shadergen.emitString(" = normalize(" + prefix + bitangent->getVariable() + ")", stage);
+ }
+ else
+ {
+ const ShaderPort* bitangent = vertexData[HW::T_BITANGENT_OBJECT];
+ shadergen.emitString(" = normalize(" + prefix + bitangent->getVariable() + ")", stage);
+ }
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/BitangentNodeMsl.h b/source/MaterialXGenMsl/Nodes/BitangentNodeMsl.h
new file mode 100644
index 0000000000..c463d045d0
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/BitangentNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_BITANGENTNODEMSL_H
+#define MATERIALX_BITANGENTNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Bitangent node implementation for MSL
+class MX_GENMSL_API BitangentNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/BlurNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/BlurNodeMsl.cpp
new file mode 100644
index 0000000000..7e050e3417
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/BlurNodeMsl.cpp
@@ -0,0 +1,27 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+#include
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr BlurNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void BlurNodeMsl::emitSamplingFunctionDefinition(const ShaderNode& /*node*/, GenContext& context, ShaderStage& stage) const
+{
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLibraryInclude("stdlib/genmsl/lib/mx_sampling.metal", context, stage);
+ shadergen.emitLineBreak(stage);
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/BlurNodeMsl.h b/source/MaterialXGenMsl/Nodes/BlurNodeMsl.h
new file mode 100644
index 0000000000..2838e83d5a
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/BlurNodeMsl.h
@@ -0,0 +1,25 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_BLURNODEMSL_H
+#define MATERIALX_BLURNODEMSL_H
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Blur node implementation for MSL
+class MX_GENMSL_API BlurNodeMsl : public BlurNode
+{
+ public:
+ static ShaderNodeImplPtr create();
+ void emitSamplingFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/FrameNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/FrameNodeMsl.cpp
new file mode 100644
index 0000000000..faac05027a
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/FrameNodeMsl.cpp
@@ -0,0 +1,35 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr FrameNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void FrameNodeMsl::createVariables(const ShaderNode&, GenContext&, Shader& shader) const
+{
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::FLOAT, HW::T_FRAME, ps);
+}
+
+void FrameNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = " + HW::T_FRAME, stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/FrameNodeMsl.h b/source/MaterialXGenMsl/Nodes/FrameNodeMsl.h
new file mode 100644
index 0000000000..f0ed8a2b0e
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/FrameNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_FRAMENODEMSL_H
+#define MATERIALX_FRAMENODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Frame node implementation for MSL
+class MX_GENMSL_API FrameNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/GeomColorNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/GeomColorNodeMsl.cpp
new file mode 100644
index 0000000000..3845e2ae76
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/GeomColorNodeMsl.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr GeomColorNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void GeomColorNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ const ShaderInput* indexInput = node.getInput(INDEX);
+ const string index = indexInput ? indexInput->getValue()->getValueString() : "0";
+
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ addStageInput(HW::VERTEX_INPUTS, Type::COLOR4, HW::T_IN_COLOR + "_" + index, vs);
+ addStageConnector(HW::VERTEX_DATA, Type::COLOR4, HW::T_COLOR + "_" + index, vs, ps);
+}
+
+void GeomColorNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ const ShaderOutput* output = node.getOutput();
+ const ShaderInput* indexInput = node.getInput(INDEX);
+ string index = indexInput ? indexInput->getValue()->getValueString() : "0";
+ string variable = HW::T_COLOR + "_" + index;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* color = vertexData[variable];
+ if (!color->isEmitted())
+ {
+ color->setEmitted();
+ shadergen.emitLine(prefix + color->getVariable() + " = " + HW::T_IN_COLOR + "_" + index, stage);
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ string suffix = "";
+ if (output->getType() == Type::FLOAT)
+ {
+ suffix = ".r";
+ }
+ else if (output->getType() == Type::COLOR3)
+ {
+ suffix = ".rgb";
+ }
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* color = vertexData[variable];
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = " + prefix + color->getVariable() + suffix, stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/GeomColorNodeMsl.h b/source/MaterialXGenMsl/Nodes/GeomColorNodeMsl.h
new file mode 100644
index 0000000000..af8a3c2a14
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/GeomColorNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_GEOMCOLORNODEMSL_H
+#define MATERIALX_GEOMCOLORNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// GeomColor node implementation for MSL
+class MX_GENMSL_API GeomColorNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/GeomPropValueNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/GeomPropValueNodeMsl.cpp
new file mode 100644
index 0000000000..2775c06f09
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/GeomPropValueNodeMsl.cpp
@@ -0,0 +1,106 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr GeomPropValueNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void GeomPropValueNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ const ShaderInput* geomPropInput = node.getInput(GEOMPROP);
+ if (!geomPropInput || !geomPropInput->getValue())
+ {
+ throw ExceptionShaderGenError("No 'geomprop' parameter found on geompropvalue node '" + node.getName() + "'. Don't know what property to bind");
+ }
+ const string geomProp = geomPropInput->getValue()->getValueString();
+ const ShaderOutput* output = node.getOutput();
+
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, output->getType(), HW::T_IN_GEOMPROP + "_" + geomProp, vs);
+ addStageConnector(HW::VERTEX_DATA, output->getType(), HW::T_IN_GEOMPROP + "_" + geomProp, vs, ps);
+}
+
+void GeomPropValueNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ const ShaderInput* geomPropInput = node.getInput(GEOMPROP);
+ if (!geomPropInput)
+ {
+ throw ExceptionShaderGenError("No 'geomprop' parameter found on geompropvalue node '" + node.getName() + "'. Don't know what property to bind");
+ }
+ const string geomname = geomPropInput->getValue()->getValueString();
+ const string variable = HW::T_IN_GEOMPROP + "_" + geomname;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* geomprop = vertexData[variable];
+ if (!geomprop->isEmitted())
+ {
+ shadergen.emitLine(prefix + geomprop->getVariable() + " = " + HW::T_IN_GEOMPROP + "_" + geomname, stage);
+ geomprop->setEmitted();
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* geomprop = vertexData[variable];
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = " + prefix + geomprop->getVariable(), stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+ShaderNodeImplPtr GeomPropValueNodeMslAsUniform::create()
+{
+ return std::make_shared();
+}
+
+void GeomPropValueNodeMslAsUniform::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ const ShaderInput* geomPropInput = node.getInput(GEOMPROP);
+ if (!geomPropInput || !geomPropInput->getValue())
+ {
+ throw ExceptionShaderGenError("No 'geomprop' parameter found on geompropvalue node '" + node.getName() + "'. Don't know what property to bind");
+ }
+ const string geomProp = geomPropInput->getValue()->getValueString();
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ ShaderPort* uniform = addStageUniform(HW::PRIVATE_UNIFORMS, node.getOutput()->getType(), HW::T_GEOMPROP + "_" + geomProp, ps);
+ uniform->setPath(geomPropInput->getPath());
+}
+
+void GeomPropValueNodeMslAsUniform::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ const ShaderInput* geomPropInput = node.getInput(GEOMPROP);
+ if (!geomPropInput)
+ {
+ throw ExceptionShaderGenError("No 'geomprop' parameter found on geompropvalue node '" + node.getName() + "'. Don't know what property to bind");
+ }
+ const string attrName = geomPropInput->getValue()->getValueString();
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = " + HW::T_GEOMPROP + "_" + attrName, stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/GeomPropValueNodeMsl.h b/source/MaterialXGenMsl/Nodes/GeomPropValueNodeMsl.h
new file mode 100644
index 0000000000..df6e540241
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/GeomPropValueNodeMsl.h
@@ -0,0 +1,39 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_GEOMPROPVALUENODEMSL_H
+#define MATERIALX_GEOMPROPVALUENODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// GeomPropValue node implementation for MSL
+class MX_GENMSL_API GeomPropValueNodeMsl : public MslImplementation
+{
+ public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+ bool isEditable(const ShaderInput& /*input*/) const override { return false; }
+};
+
+/// GeomPropValue node non-implementation for MSL
+class MX_GENMSL_API GeomPropValueNodeMslAsUniform : public MslImplementation
+{
+ public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/HeightToNormalNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/HeightToNormalNodeMsl.cpp
new file mode 100644
index 0000000000..85cb302cb9
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/HeightToNormalNodeMsl.cpp
@@ -0,0 +1,113 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+namespace
+{
+ /// Name of filter function to call to compute normals from input samples
+ const string filterFunctionName = "mx_normal_from_samples_sobel";
+
+ /// Name of function to compute sample size in uv space. Takes uv, filter size, and filter offset
+ /// as input, and return a 2 channel vector as output
+ const string sampleSizeFunctionUV = "mx_compute_sample_size_uv";
+
+ const unsigned int sampleCount = 9;
+ const unsigned int filterWidth = 3;
+ const float filterSize = 1.0;
+ const float filterOffset = 0.0;
+}
+
+ShaderNodeImplPtr HeightToNormalNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void HeightToNormalNodeMsl::computeSampleOffsetStrings(const string& sampleSizeName, const string& offsetTypeString,
+ unsigned int, StringVec& offsetStrings) const
+{
+ // Build a 3x3 grid of samples that are offset by the provided sample size
+ for (int row = -1; row <= 1; row++)
+ {
+ for (int col = -1; col <= 1; col++)
+ {
+ offsetStrings.push_back(" + " + sampleSizeName + " * " + offsetTypeString + "(" + std::to_string(float(col)) + "," + std::to_string(float(row)) + ")");
+ }
+ }
+}
+
+bool HeightToNormalNodeMsl::acceptsInputType(const TypeDesc* type) const
+{
+ // Only support inputs which are float scalar
+ return (type == Type::FLOAT && type->isScalar());
+}
+
+void HeightToNormalNodeMsl::emitFunctionDefinition(const ShaderNode&, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ // Emit sampling functions
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLibraryInclude("stdlib/genglsl/lib/mx_sampling.glsl", context, stage);
+ shadergen.emitLineBreak(stage);
+ }
+}
+
+void HeightToNormalNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+
+ const ShaderInput* inInput = node.getInput("in");
+ const ShaderInput* scaleInput = node.getInput("scale");
+
+ if (!inInput || !scaleInput)
+ {
+ throw ExceptionShaderGenError("Node '" + node.getName() + "' is not a valid heighttonormal node");
+ }
+
+ // Create the input "samples". This means to emit the calls to
+ // compute the sames and return a set of strings containaing
+ // the variables to assign to the sample grid.
+ //
+ StringVec sampleStrings;
+ emitInputSamplesUV(node, sampleCount, filterWidth,
+ filterSize, filterOffset, sampleSizeFunctionUV,
+ context, stage, sampleStrings);
+
+ const ShaderOutput* output = node.getOutput();
+
+ // Emit code to evaluate samples.
+ //
+ string sampleName(output->getVariable() + "_samples");
+ shadergen.emitLine("float " + sampleName + "[" + std::to_string(sampleCount) + "]", stage);
+ for (unsigned int i = 0; i < sampleCount; i++)
+ {
+ shadergen.emitLine(sampleName + "[" + std::to_string(i) + "] = " + sampleStrings[i], stage);
+ }
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(output, true, false, context, stage);
+ shadergen.emitString(" = " + filterFunctionName, stage);
+ shadergen.emitString("(" + sampleName + ", ", stage);
+ shadergen.emitInput(scaleInput, context, stage);
+ shadergen.emitString(")", stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+const string& HeightToNormalNodeMsl::getTarget() const
+{
+ return MslShaderGenerator::TARGET;
+}
+
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/HeightToNormalNodeMsl.h b/source/MaterialXGenMsl/Nodes/HeightToNormalNodeMsl.h
new file mode 100644
index 0000000000..66d473b872
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/HeightToNormalNodeMsl.h
@@ -0,0 +1,37 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_HEIGHTTONORMALNODEMSL_H
+#define MATERIALX_HEIGHTTONORMALNODEMSL_H
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// HeightToNormal node implementation for MSL
+class MX_GENMSL_API HeightToNormalNodeMsl : public ConvolutionNode
+{
+ public:
+ static ShaderNodeImplPtr create();
+
+ void emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+ const string& getTarget() const override;
+
+ protected:
+ /// Return if given type is an acceptible input
+ bool acceptsInputType(const TypeDesc* type) const override;
+
+ /// Compute offset strings for sampling
+ void computeSampleOffsetStrings(const string& sampleSizeName, const string& offsetTypeString,
+ unsigned int filterWidth, StringVec& offsetStrings) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/LightCompoundNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/LightCompoundNodeMsl.cpp
new file mode 100644
index 0000000000..f634278f5b
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightCompoundNodeMsl.cpp
@@ -0,0 +1,138 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+LightCompoundNodeMsl::LightCompoundNodeMsl() :
+ _lightUniforms(HW::LIGHT_DATA, EMPTY_STRING)
+{
+}
+
+ShaderNodeImplPtr LightCompoundNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+const string& LightCompoundNodeMsl::getTarget() const
+{
+ return MslShaderGenerator::TARGET;
+}
+
+void LightCompoundNodeMsl::initialize(const InterfaceElement& element, GenContext& context)
+{
+ CompoundNode::initialize(element, context);
+
+ // Store light uniforms for all inputs on the interface
+ const NodeGraph& graph = static_cast(element);
+ NodeDefPtr nodeDef = graph.getNodeDef();
+ for (InputPtr input : nodeDef->getActiveInputs())
+ {
+ _lightUniforms.add(TypeDesc::get(input->getType()), input->getName());
+ }
+}
+
+void LightCompoundNodeMsl::createVariables(const ShaderNode&, GenContext& context, Shader& shader) const
+{
+ // Create variables for all child nodes
+ for (ShaderNode* childNode : _rootGraph->getNodes())
+ {
+ childNode->getImplementation().createVariables(*childNode, context, shader);
+ }
+
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ VariableBlock& lightData = ps.getUniformBlock(HW::LIGHT_DATA);
+
+ // Create all light uniforms
+ for (size_t i = 0; i<_lightUniforms.size(); ++i)
+ {
+ ShaderPort* u = const_cast(_lightUniforms[i]);
+ lightData.add(u->getSelf());
+ }
+
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ shadergen.addStageLightingUniforms(context, ps);
+}
+
+void LightCompoundNodeMsl::emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ // Emit functions for all child nodes
+ shadergen.emitFunctionDefinitions(*_rootGraph, context, stage);
+
+ // Find any closure contexts used by this node
+ // and emit the function for each context.
+ vector ccts;
+ shadergen.getClosureContexts(node, ccts);
+ if (ccts.empty())
+ {
+ emitFunctionDefinition(nullptr, context, stage);
+ }
+ else
+ {
+ for (ClosureContext* cct : ccts)
+ {
+ emitFunctionDefinition(cct, context, stage);
+ }
+ }
+ }
+}
+
+void LightCompoundNodeMsl::emitFunctionDefinition(ClosureContext* cct, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ // Emit function signature
+ if (cct)
+ {
+ // Use the first output for classifying node type for the closure context.
+ // This is only relevent for closures, and they only have a single output.
+ const TypeDesc* nodeType = _rootGraph->getOutputSocket()->getType();
+ shadergen.emitLine("void " + _functionName + cct->getSuffix(nodeType) + "(LightData light, float3 position, out lightshader result)", stage, false);
+ }
+ else
+ {
+ shadergen.emitLine("void " + _functionName + "(LightData light, float3 position, out lightshader result)", stage, false);
+ }
+
+ shadergen.emitFunctionBodyBegin(*_rootGraph, context, stage);
+
+ // Emit all texturing nodes. These are inputs to any
+ // closure/shader nodes and need to be emitted first.
+ shadergen.emitFunctionCalls(*_rootGraph, context, stage, ShaderNode::Classification::TEXTURE);
+
+ // Emit function calls for all light shader nodes.
+ // These will internally emit their closure function calls.
+ if (cct)
+ {
+ context.pushClosureContext(cct);
+ shadergen.emitFunctionCalls(*_rootGraph, context, stage, ShaderNode::Classification::SHADER | ShaderNode::Classification::LIGHT);
+ context.popClosureContext();
+ }
+ else
+ {
+ shadergen.emitFunctionCalls(*_rootGraph, context, stage, ShaderNode::Classification::SHADER | ShaderNode::Classification::LIGHT);
+ }
+
+ shadergen.emitFunctionBodyEnd(*_rootGraph, context, stage);
+}
+
+void LightCompoundNodeMsl::emitFunctionCall(const ShaderNode&, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLine(_functionName + "(light, position, result)", stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/LightCompoundNodeMsl.h b/source/MaterialXGenMsl/Nodes/LightCompoundNodeMsl.h
new file mode 100644
index 0000000000..a4e4bf3847
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightCompoundNodeMsl.h
@@ -0,0 +1,45 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_LIGHTCOMPOUNDNODEMSL_H
+#define MATERIALX_LIGHTCOMPOUNDNODEMSL_H
+
+#include
+
+#include
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+class MslShaderGenerator;
+
+/// LightCompound node implementation for MSL
+class MX_GENMSL_API LightCompoundNodeMsl : public CompoundNode
+{
+public:
+ LightCompoundNodeMsl();
+
+ static ShaderNodeImplPtr create();
+
+ const string& getTarget() const override;
+
+ void initialize(const InterfaceElement& element, GenContext& context) override;
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+protected:
+ void emitFunctionDefinition(ClosureContext* cct, GenContext& context, ShaderStage& stage) const;
+
+ VariableBlock _lightUniforms;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/LightNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/LightNodeMsl.cpp
new file mode 100644
index 0000000000..61eb3b00e5
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightNodeMsl.cpp
@@ -0,0 +1,95 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+namespace
+{
+ const string LIGHT_DIRECTION_CALCULATION =
+ "float3 L = light.position - position;\n"
+ "float distance = length(L);\n"
+ "L /= distance;\n"
+ "result.direction = L;\n";
+}
+
+LightNodeMsl::LightNodeMsl() :
+ _callEmission(HwShaderGenerator::ClosureContextType::EMISSION)
+{
+ // Emission context
+ _callEmission.addArgument(Type::EDF, ClosureContext::Argument(Type::VECTOR3, "light.direction"));
+ _callEmission.addArgument(Type::EDF, ClosureContext::Argument(Type::VECTOR3, "-L"));
+}
+
+ShaderNodeImplPtr LightNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void LightNodeMsl::createVariables(const ShaderNode&, GenContext& context, Shader& shader) const
+{
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ // Create uniform for intensity, exposure and direction
+ VariableBlock& lightUniforms = ps.getUniformBlock(HW::LIGHT_DATA);
+ lightUniforms.add(Type::FLOAT, "intensity", Value::createValue(1.0f));
+ lightUniforms.add(Type::FLOAT, "exposure", Value::createValue(0.0f));
+ lightUniforms.add(Type::VECTOR3, "direction", Value::createValue(Vector3(0.0f,1.0f,0.0f)));
+
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ shadergen.addStageLightingUniforms(context, ps);
+}
+
+void LightNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ shadergen.emitBlock(LIGHT_DIRECTION_CALCULATION, FilePath(), context, stage);
+ shadergen.emitLineBreak(stage);
+
+ const ShaderInput* edfInput = node.getInput("edf");
+ const ShaderNode* edf = edfInput->getConnectedSibling();
+ if (edf)
+ {
+ context.pushClosureContext(&_callEmission);
+ shadergen.emitFunctionCall(*edf, context, stage);
+ context.popClosureContext();
+
+ shadergen.emitLineBreak(stage);
+
+ shadergen.emitComment("Apply quadratic falloff and adjust intensity", stage);
+ shadergen.emitLine("result.intensity = " + edf->getOutput()->getVariable() + " / (distance * distance)", stage);
+
+ const ShaderInput* intensity = node.getInput("intensity");
+ const ShaderInput* exposure = node.getInput("exposure");
+
+ shadergen.emitLineBegin(stage);
+ shadergen.emitString("result.intensity *= ", stage);
+ shadergen.emitInput(intensity, context, stage);
+ shadergen.emitLineEnd(stage);
+
+ // Emit exposure adjustment only if it matters
+ if (exposure->getConnection() || (exposure->getValue() && exposure->getValue()->asA() != 0.0f))
+ {
+ shadergen.emitLineBegin(stage);
+ shadergen.emitString("result.intensity *= pow(2, ", stage);
+ shadergen.emitInput(exposure, context, stage);
+ shadergen.emitString(")", stage);
+ shadergen.emitLineEnd(stage);
+ }
+ }
+ else
+ {
+ shadergen.emitLine("result.intensity = float3(0.0)", stage);
+ }
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/LightNodeMsl.h b/source/MaterialXGenMsl/Nodes/LightNodeMsl.h
new file mode 100644
index 0000000000..92509f61ed
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightNodeMsl.h
@@ -0,0 +1,31 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_LIGHTNODEMSL_H
+#define MATERIALX_LIGHTNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Light node implementation for MSL
+class MX_GENMSL_API LightNodeMsl : public MslImplementation
+{
+ public:
+ LightNodeMsl();
+
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+ private:
+ mutable ClosureContext _callEmission;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/LightSamplerNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/LightSamplerNodeMsl.cpp
new file mode 100644
index 0000000000..478642255e
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightSamplerNodeMsl.cpp
@@ -0,0 +1,55 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+namespace
+{
+ const string SAMPLE_LIGHTS_FUNC_SIGNATURE = "void sampleLightSource(LightData light, float3 position, out lightshader result)";
+}
+
+LightSamplerNodeMsl::LightSamplerNodeMsl()
+{
+ _hash = std::hash{}(SAMPLE_LIGHTS_FUNC_SIGNATURE);
+}
+
+ShaderNodeImplPtr LightSamplerNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void LightSamplerNodeMsl::emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+
+ // Emit light sampler function with all bound light types
+ shadergen.emitLine(SAMPLE_LIGHTS_FUNC_SIGNATURE, stage, false);
+ shadergen.emitFunctionBodyBegin(node, context, stage);
+ shadergen.emitLine("result.intensity = float3(0.0)", stage);
+ shadergen.emitLine("result.direction = float3(0.0)", stage);
+
+ HwLightShadersPtr lightShaders = context.getUserData(HW::USER_DATA_LIGHT_SHADERS);
+ if (lightShaders)
+ {
+ string ifstatement = "if ";
+ for (const auto& it : lightShaders->get())
+ {
+ shadergen.emitLine(ifstatement + "(light.type == " + std::to_string(it.first) + ")", stage, false);
+ shadergen.emitScopeBegin(stage);
+ shadergen.emitFunctionCall(*it.second, context, stage, false);
+ shadergen.emitScopeEnd(stage);
+ ifstatement = "else if ";
+ }
+ }
+
+ shadergen.emitFunctionBodyEnd(node, context, stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/LightSamplerNodeMsl.h b/source/MaterialXGenMsl/Nodes/LightSamplerNodeMsl.h
new file mode 100644
index 0000000000..42f2978076
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightSamplerNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_LIGHTSAMPLERNODEMSL_H
+#define MATERIALX_LIGHTSAMPLERNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Utility node for sampling lights for MSL.
+class MX_GENMSL_API LightSamplerNodeMsl : public MslImplementation
+{
+public:
+ LightSamplerNodeMsl();
+
+ static ShaderNodeImplPtr create();
+
+ void emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/LightShaderNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/LightShaderNodeMsl.cpp
new file mode 100644
index 0000000000..b556415dae
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightShaderNodeMsl.cpp
@@ -0,0 +1,76 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+LightShaderNodeMsl::LightShaderNodeMsl() :
+ _lightUniforms(HW::LIGHT_DATA, EMPTY_STRING)
+{
+}
+
+ShaderNodeImplPtr LightShaderNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+const string& LightShaderNodeMsl::getTarget() const
+{
+ return MslShaderGenerator::TARGET;
+}
+
+void LightShaderNodeMsl::initialize(const InterfaceElement& element, GenContext& context)
+{
+ SourceCodeNode::initialize(element, context);
+
+ if (_inlined)
+ {
+ throw ExceptionShaderGenError("Light shaders doesn't support inlined implementations'");
+ }
+
+ if (!element.isA())
+ {
+ throw ExceptionShaderGenError("Element '" + element.getName() + "' is not an Implementation element");
+ }
+
+ const Implementation& impl = static_cast(element);
+
+ // Store light uniforms for all inputs on the interface
+ NodeDefPtr nodeDef = impl.getNodeDef();
+ for (InputPtr input : nodeDef->getActiveInputs())
+ {
+ _lightUniforms.add(TypeDesc::get(input->getType()), input->getName(), input->getValue());
+ }
+}
+
+void LightShaderNodeMsl::createVariables(const ShaderNode&, GenContext& context, Shader& shader) const
+{
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ // Create all light uniforms
+ VariableBlock& lightData = ps.getUniformBlock(HW::LIGHT_DATA);
+ for (size_t i = 0; i < _lightUniforms.size(); ++i)
+ {
+ const ShaderPort* u = _lightUniforms[i];
+ lightData.add(u->getType(), u->getName());
+ }
+
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ shadergen.addStageLightingUniforms(context, ps);
+}
+
+void LightShaderNodeMsl::emitFunctionCall(const ShaderNode&, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLine(_functionName + "(light, position, result)", stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/LightShaderNodeMsl.h b/source/MaterialXGenMsl/Nodes/LightShaderNodeMsl.h
new file mode 100644
index 0000000000..ec44894bdd
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/LightShaderNodeMsl.h
@@ -0,0 +1,37 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_LIGHTSHADERNODEMSL_H
+#define MATERIALX_LIGHTSHADERNODEMSL_H
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// LightShader node implementation for MSL
+/// Used for all light shaders implemented in source code.
+class MX_GENMSL_API LightShaderNodeMsl : public SourceCodeNode
+{
+public:
+ LightShaderNodeMsl();
+
+ static ShaderNodeImplPtr create();
+
+ const string& getTarget() const override;
+
+ void initialize(const InterfaceElement& element, GenContext& context) override;
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+protected:
+ VariableBlock _lightUniforms;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/NormalNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/NormalNodeMsl.cpp
new file mode 100644
index 0000000000..6250290960
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/NormalNodeMsl.cpp
@@ -0,0 +1,88 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr NormalNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void NormalNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_NORMAL, vs);
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+ if (space == WORLD_SPACE)
+ {
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::MATRIX44, HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX, vs);
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_NORMAL_WORLD, vs, ps);
+ }
+ else
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_NORMAL_OBJECT, vs, ps);
+ }
+}
+
+void NormalNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ if (space == WORLD_SPACE)
+ {
+ ShaderPort* normal = vertexData[HW::T_NORMAL_WORLD];
+ if (!normal->isEmitted())
+ {
+ normal->setEmitted();
+ shadergen.emitLine(prefix + normal->getVariable() + " = normalize((" + HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX + " * float4(" + HW::T_IN_NORMAL + ", 0.0)).xyz)", stage);
+ }
+ }
+ else
+ {
+ ShaderPort* normal = vertexData[HW::T_NORMAL_OBJECT];
+ if (!normal->isEmitted())
+ {
+ normal->setEmitted();
+ shadergen.emitLine(prefix + normal->getVariable() + " = " + HW::T_IN_NORMAL, stage);
+ }
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ if (space == WORLD_SPACE)
+ {
+ const ShaderPort* normal = vertexData[HW::T_NORMAL_WORLD];
+ shadergen.emitString(" = normalize(" + prefix + normal->getVariable() + ")", stage);
+ }
+ else
+ {
+ const ShaderPort* normal = vertexData[HW::T_NORMAL_OBJECT];
+ shadergen.emitString(" = normalize(" + prefix + normal->getVariable() + ")", stage);
+ }
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/NormalNodeMsl.h b/source/MaterialXGenMsl/Nodes/NormalNodeMsl.h
new file mode 100644
index 0000000000..beb92c696c
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/NormalNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_NORMALNODEMSL_H
+#define MATERIALX_NORMALNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Normal node implementation for MSL
+class MX_GENMSL_API NormalNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/NumLightsNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/NumLightsNodeMsl.cpp
new file mode 100644
index 0000000000..57af5bfc06
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/NumLightsNodeMsl.cpp
@@ -0,0 +1,47 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+namespace
+{
+ const string NUM_LIGHTS_FUNC_SIGNATURE = "int numActiveLightSources()";
+}
+
+NumLightsNodeMsl::NumLightsNodeMsl()
+{
+ _hash = std::hash{}(NUM_LIGHTS_FUNC_SIGNATURE);
+}
+
+ShaderNodeImplPtr NumLightsNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void NumLightsNodeMsl::createVariables(const ShaderNode&, GenContext&, Shader& shader) const
+{
+ // Create uniform for number of active light sources
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ ShaderPort* numActiveLights = addStageUniform(HW::PRIVATE_UNIFORMS, Type::INTEGER, HW::T_NUM_ACTIVE_LIGHT_SOURCES, ps);
+ numActiveLights->setValue(Value::createValue(0));
+}
+
+void NumLightsNodeMsl::emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLine(NUM_LIGHTS_FUNC_SIGNATURE, stage, false);
+ shadergen.emitFunctionBodyBegin(node, context, stage);
+ shadergen.emitLine("return min(" + HW::T_NUM_ACTIVE_LIGHT_SOURCES + ", " + HW::LIGHT_DATA_MAX_LIGHT_SOURCES + ") ", stage);
+ shadergen.emitFunctionBodyEnd(node, context, stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/NumLightsNodeMsl.h b/source/MaterialXGenMsl/Nodes/NumLightsNodeMsl.h
new file mode 100644
index 0000000000..d06e904766
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/NumLightsNodeMsl.h
@@ -0,0 +1,28 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_NUMLIGHTSNODEMSL_H
+#define MATERIALX_NUMLIGHTSNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Utility node for getting number of active lights for MSL.
+class MX_GENMSL_API NumLightsNodeMsl : public MslImplementation
+{
+public:
+ NumLightsNodeMsl();
+
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/PositionNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/PositionNodeMsl.cpp
new file mode 100644
index 0000000000..6f45f4364b
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/PositionNodeMsl.cpp
@@ -0,0 +1,87 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr PositionNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void PositionNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ ShaderStage vs = shader.getStage(Stage::VERTEX);
+ ShaderStage ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_POSITION, vs);
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+ if (space == WORLD_SPACE)
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_POSITION_WORLD, vs, ps);
+ }
+ else
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_POSITION_OBJECT, vs, ps);
+ }
+}
+
+void PositionNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ if (space == WORLD_SPACE)
+ {
+ ShaderPort* position = vertexData[HW::T_POSITION_WORLD];
+ if (!position->isEmitted())
+ {
+ position->setEmitted();
+ shadergen.emitLine(prefix + position->getVariable() + " = hPositionWorld.xyz", stage);
+ }
+ }
+ else
+ {
+ ShaderPort* position = vertexData[HW::T_POSITION_OBJECT];
+ if (!position->isEmitted())
+ {
+ position->setEmitted();
+ shadergen.emitLine(prefix + position->getVariable() + " = " + HW::T_IN_POSITION, stage);
+ }
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ if (space == WORLD_SPACE)
+ {
+ const ShaderPort* position = vertexData[HW::T_POSITION_WORLD];
+ shadergen.emitString(" = " + prefix + position->getVariable(), stage);
+ }
+ else
+ {
+ const ShaderPort* position = vertexData[HW::T_POSITION_OBJECT];
+ shadergen.emitString(" = " + prefix + position->getVariable(), stage);
+ }
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/PositionNodeMsl.h b/source/MaterialXGenMsl/Nodes/PositionNodeMsl.h
new file mode 100644
index 0000000000..bd3d4266c8
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/PositionNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_POSITIONNODEMSL_H
+#define MATERIALX_POSITIONNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Position node implementation for MSL
+class MX_GENMSL_API PositionNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/SurfaceNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/SurfaceNodeMsl.cpp
new file mode 100644
index 0000000000..fb31689bd2
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/SurfaceNodeMsl.cpp
@@ -0,0 +1,272 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+SurfaceNodeMsl::SurfaceNodeMsl() :
+ _callReflection(HwShaderGenerator::ClosureContextType::REFLECTION),
+ _callTransmission(HwShaderGenerator::ClosureContextType::TRANSMISSION),
+ _callIndirect(HwShaderGenerator::ClosureContextType::INDIRECT),
+ _callEmission(HwShaderGenerator::ClosureContextType::EMISSION)
+{
+ // Create closure contexts for calling closure functions.
+ //
+ // Reflection context
+ _callReflection.setSuffix(Type::BSDF, HwShaderGenerator::CLOSURE_CONTEXT_SUFFIX_REFLECTION);
+ _callReflection.addArgument(Type::BSDF, ClosureContext::Argument(Type::VECTOR3, HW::DIR_L));
+ _callReflection.addArgument(Type::BSDF, ClosureContext::Argument(Type::VECTOR3, HW::DIR_V));
+ _callReflection.addArgument(Type::BSDF, ClosureContext::Argument(Type::VECTOR3, HW::WORLD_POSITION));
+ _callReflection.addArgument(Type::BSDF, ClosureContext::Argument(Type::FLOAT, HW::OCCLUSION));
+ // Transmission context
+ _callTransmission.setSuffix(Type::BSDF, HwShaderGenerator::CLOSURE_CONTEXT_SUFFIX_TRANSMISSION);
+ _callTransmission.addArgument(Type::BSDF, ClosureContext::Argument(Type::VECTOR3, HW::DIR_V));
+ // Indirect/Environment context
+ _callIndirect.setSuffix(Type::BSDF, HwShaderGenerator::CLOSURE_CONTEXT_SUFFIX_INDIRECT);
+ _callIndirect.addArgument(Type::BSDF, ClosureContext::Argument(Type::VECTOR3, HW::DIR_V));
+ // Emission context
+ _callEmission.addArgument(Type::EDF, ClosureContext::Argument(Type::VECTOR3, HW::DIR_N));
+ _callEmission.addArgument(Type::EDF, ClosureContext::Argument(Type::VECTOR3, HW::DIR_V));
+}
+
+ShaderNodeImplPtr SurfaceNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void SurfaceNodeMsl::createVariables(const ShaderNode&, GenContext& context, Shader& shader) const
+{
+ // TODO:
+ // The surface shader needs position, normal, view position and light sources. We should solve this by adding some
+ // dependency mechanism so this implementation can be set to depend on the PositionNodeMsl, NormalNodeMsl
+ // ViewDirectionNodeMsl and LightNodeMsl nodes instead? This is where the MaterialX attribute "internalgeomprops"
+ // is needed.
+ //
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_POSITION, vs);
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_NORMAL, vs);
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::MATRIX44, HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX, vs);
+
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_POSITION_WORLD, vs, ps);
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_NORMAL_WORLD, vs, ps);
+
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::VECTOR3, HW::T_VIEW_POSITION, ps);
+
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ shadergen.addStageLightingUniforms(context, ps);
+}
+
+void SurfaceNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* position = vertexData[HW::T_POSITION_WORLD];
+ if (!position->isEmitted())
+ {
+ position->setEmitted();
+ shadergen.emitLine(prefix + position->getVariable() + " = hPositionWorld.xyz", stage);
+ }
+ ShaderPort* normal = vertexData[HW::T_NORMAL_WORLD];
+ if (!normal->isEmitted())
+ {
+ normal->setEmitted();
+ shadergen.emitLine(prefix + normal->getVariable() + " = normalize((" + HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX + " * float4(" + HW::T_IN_NORMAL + ", 0)).xyz)", stage);
+ }
+ if (context.getOptions().hwAmbientOcclusion)
+ {
+ ShaderPort* texcoord = vertexData[HW::T_TEXCOORD + "_0"];
+ if (!texcoord->isEmitted())
+ {
+ texcoord->setEmitted();
+ shadergen.emitLine(prefix + texcoord->getVariable() + " = " + HW::T_IN_TEXCOORD + "_0", stage);
+ }
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+
+ // Declare the output variable
+ const ShaderOutput* output = node.getOutput();
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(output, true, true, context, stage);
+ shadergen.emitLineEnd(stage);
+
+ shadergen.emitScopeBegin(stage);
+
+ shadergen.emitLine("float3 N = normalize(" + prefix + HW::T_NORMAL_WORLD + ")", stage);
+ shadergen.emitLine("float3 V = normalize(" + HW::T_VIEW_POSITION + " - " + prefix + HW::T_POSITION_WORLD + ")", stage);
+ shadergen.emitLine("float3 P = " + prefix + HW::T_POSITION_WORLD, stage);
+ shadergen.emitLineBreak(stage);
+
+ const string outColor = output->getVariable() + ".color";
+ const string outTransparency = output->getVariable() + ".transparency";
+
+ const ShaderInput* bsdfInput = node.getInput("bsdf");
+ const ShaderNode* bsdf = bsdfInput->getConnectedSibling();
+ if (bsdf)
+ {
+ shadergen.emitLineBegin(stage);
+ shadergen.emitString("float surfaceOpacity = ", stage);
+ shadergen.emitInput(node.getInput("opacity"), context, stage);
+ shadergen.emitLineEnd(stage);
+ shadergen.emitLineBreak(stage);
+
+ //
+ // Handle direct lighting
+ //
+ shadergen.emitComment("Shadow occlusion", stage);
+ if (context.getOptions().hwShadowMap)
+ {
+ shadergen.emitLine("float3 shadowCoord = (" + HW::T_SHADOW_MATRIX + " * float4(" + prefix + HW::T_POSITION_WORLD + ", 1.0)).xyz", stage);
+ shadergen.emitLine("shadowCoord.xy = shadowCoord.xy * 0.5 + 0.5", stage);
+
+ shadergen.emitLine("float2 shadowMoments = texture(" + HW::T_SHADOW_MAP + ", shadowCoord.xy).xy", stage);
+ shadergen.emitLine("float occlusion = mx_variance_shadow_occlusion(shadowMoments, shadowCoord.z)", stage);
+ }
+ else
+ {
+ shadergen.emitLine("float occlusion = 1.0", stage);
+ }
+ shadergen.emitLineBreak(stage);
+
+ emitLightLoop(node, context, stage, outColor);
+
+ //
+ // Handle indirect lighting.
+ //
+ shadergen.emitComment("Ambient occlusion", stage);
+ if (context.getOptions().hwAmbientOcclusion)
+ {
+ ShaderPort* texcoord = vertexData[HW::T_TEXCOORD + "_0"];
+ shadergen.emitLine("float2 ambOccUv = mx_transform_uv(" + prefix + texcoord->getVariable() + ", float2(1.0), float2(0.0))", stage);
+ shadergen.emitLine("occlusion = mix(1.0, texture(" + HW::T_AMB_OCC_MAP + ", ambOccUv).x, " + HW::T_AMB_OCC_GAIN + ")", stage);
+ }
+ else
+ {
+ shadergen.emitLine("occlusion = 1.0", stage);
+ }
+ shadergen.emitLineBreak(stage);
+
+ shadergen.emitComment("Add environment contribution", stage);
+ shadergen.emitScopeBegin(stage);
+
+ context.pushClosureContext(&_callIndirect);
+ shadergen.emitFunctionCall(*bsdf, context, stage);
+ context.popClosureContext();
+
+ shadergen.emitLineBreak(stage);
+ shadergen.emitLine(outColor + " += occlusion * " + bsdf->getOutput()->getVariable() + ".response", stage);
+ shadergen.emitScopeEnd(stage);
+ shadergen.emitLineBreak(stage);
+ }
+
+ //
+ // Handle surface emission.
+ //
+ const ShaderInput* edfInput = node.getInput("edf");
+ const ShaderNode* edf = edfInput->getConnectedSibling();
+ if (edf)
+ {
+ shadergen.emitComment("Add surface emission", stage);
+ shadergen.emitScopeBegin(stage);
+
+ context.pushClosureContext(&_callEmission);
+ shadergen.emitFunctionCall(*edf, context, stage);
+ context.popClosureContext();
+
+ shadergen.emitLine(outColor + " += " + edf->getOutput()->getVariable(), stage);
+ shadergen.emitScopeEnd(stage);
+ shadergen.emitLineBreak(stage);
+ }
+
+ //
+ // Handle surface transmission and opacity.
+ //
+ if (bsdf)
+ {
+ shadergen.emitComment("Calculate the BSDF transmission for viewing direction", stage);
+ shadergen.emitScopeBegin(stage);
+ context.pushClosureContext(&_callTransmission);
+ shadergen.emitFunctionCall(*bsdf, context, stage);
+ if (context.getOptions().hwTransmissionRenderMethod == TRANSMISSION_REFRACTION)
+ {
+ shadergen.emitLine(outColor + " += " + bsdf->getOutput()->getVariable() + ".response", stage);
+ }
+ else
+ {
+ shadergen.emitLine(outTransparency + " += " + bsdf->getOutput()->getVariable() + ".response", stage);
+ }
+ shadergen.emitScopeEnd(stage);
+ context.popClosureContext();
+
+ shadergen.emitLineBreak(stage);
+ shadergen.emitComment("Compute and apply surface opacity", stage);
+ shadergen.emitScopeBegin(stage);
+ shadergen.emitLine(outColor + " *= surfaceOpacity", stage);
+ shadergen.emitLine(outTransparency + " = mix(float3(1.0), " + outTransparency + ", surfaceOpacity)", stage);
+ shadergen.emitScopeEnd(stage);
+ }
+
+ shadergen.emitScopeEnd(stage);
+ shadergen.emitLineBreak(stage);
+
+ }
+}
+
+void SurfaceNodeMsl::emitLightLoop(const ShaderNode& node, GenContext& context, ShaderStage& stage, const string& outColor) const
+{
+ //
+ // Generate Light loop if requested
+ //
+ if (context.getOptions().hwMaxActiveLightSources > 0)
+ {
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ const VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+
+ const ShaderInput* bsdfInput = node.getInput("bsdf");
+ const ShaderNode* bsdf = bsdfInput->getConnectedSibling();
+
+ shadergen.emitComment("Light loop", stage);
+ shadergen.emitLine("int numLights = numActiveLightSources()", stage);
+ shadergen.emitLine("lightshader lightShader", stage);
+ shadergen.emitLine("for (int activeLightIndex = 0; activeLightIndex < numLights; ++activeLightIndex)", stage, false);
+
+ shadergen.emitScopeBegin(stage);
+
+ shadergen.emitLine("sampleLightSource(" + HW::T_LIGHT_DATA_INSTANCE + "[activeLightIndex], " + prefix + HW::T_POSITION_WORLD + ", lightShader)", stage);
+ shadergen.emitLine("float3 L = lightShader.direction", stage);
+ shadergen.emitLineBreak(stage);
+
+ shadergen.emitComment("Calculate the BSDF response for this light source", stage);
+ context.pushClosureContext(&_callReflection);
+ shadergen.emitFunctionCall(*bsdf, context, stage);
+ context.popClosureContext();
+
+ shadergen.emitLineBreak(stage);
+
+ shadergen.emitComment("Accumulate the light's contribution", stage);
+ shadergen.emitLine(outColor + " += lightShader.intensity * " + bsdf->getOutput()->getVariable() + ".response", stage);
+
+ shadergen.emitScopeEnd(stage);
+ shadergen.emitLineBreak(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/SurfaceNodeMsl.h b/source/MaterialXGenMsl/Nodes/SurfaceNodeMsl.h
new file mode 100644
index 0000000000..380e8e50c2
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/SurfaceNodeMsl.h
@@ -0,0 +1,38 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_SURFACENODEMSL_H
+#define MATERIALX_SURFACENODEMSL_H
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Surface node implementation for MSL
+class MX_GENMSL_API SurfaceNodeMsl : public MslImplementation
+{
+ public:
+ SurfaceNodeMsl();
+
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+ virtual void emitLightLoop(const ShaderNode& node, GenContext& context, ShaderStage& stage, const string& outColor) const;
+
+ protected:
+ /// Closure contexts for calling closure functions.
+ mutable ClosureContext _callReflection;
+ mutable ClosureContext _callTransmission;
+ mutable ClosureContext _callIndirect;
+ mutable ClosureContext _callEmission;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/SurfaceShaderNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/SurfaceShaderNodeMsl.cpp
new file mode 100644
index 0000000000..59f846ce22
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/SurfaceShaderNodeMsl.cpp
@@ -0,0 +1,65 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr SurfaceShaderNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+const string& SurfaceShaderNodeMsl::getTarget() const
+{
+ return MslShaderGenerator::TARGET;
+}
+
+void SurfaceShaderNodeMsl::createVariables(const ShaderNode&, GenContext& context, Shader& shader) const
+{
+ // TODO:
+ // The surface shader needs position, view position and light sources. We should solve this by adding some
+ // dependency mechanism so this implementation can be set to depend on the PositionNodeMsl,
+ // ViewDirectionNodeMsl and LightNodeMsl nodes instead? This is where the MaterialX attribute "internalgeomprops"
+ // is needed.
+ //
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_POSITION, vs);
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_POSITION_WORLD, vs, ps);
+
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::VECTOR3, HW::T_VIEW_POSITION, ps);
+
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+ shadergen.addStageLightingUniforms(context, ps);
+}
+
+void SurfaceShaderNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* position = vertexData[HW::T_POSITION_WORLD];
+ if (!position->isEmitted())
+ {
+ position->setEmitted();
+ context.getShaderGenerator().emitLine(prefix + position->getVariable() + " = hPositionWorld.xyz", stage);
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ SourceCodeNode::emitFunctionCall(node, context, stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/SurfaceShaderNodeMsl.h b/source/MaterialXGenMsl/Nodes/SurfaceShaderNodeMsl.h
new file mode 100644
index 0000000000..361d2b2e16
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/SurfaceShaderNodeMsl.h
@@ -0,0 +1,30 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_SURFACESHADERNODEMSL_H
+#define MATERIALX_SURFACESHADERNODEMSL_H
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// SurfaceShader node implementation for MSL
+/// Used for all surface shaders implemented in source code.
+class MX_GENMSL_API SurfaceShaderNodeMsl : public SourceCodeNode
+{
+ public:
+ static ShaderNodeImplPtr create();
+
+ const string& getTarget() const override;
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/TangentNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/TangentNodeMsl.cpp
new file mode 100644
index 0000000000..5e557e4e30
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TangentNodeMsl.cpp
@@ -0,0 +1,88 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr TangentNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void TangentNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, Type::VECTOR3, HW::T_IN_TANGENT, vs);
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+ if (space == WORLD_SPACE)
+ {
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::MATRIX44, HW::T_WORLD_MATRIX, vs);
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_TANGENT_WORLD, vs, ps);
+ }
+ else
+ {
+ addStageConnector(HW::VERTEX_DATA, Type::VECTOR3, HW::T_TANGENT_OBJECT, vs, ps);
+ }
+}
+
+void TangentNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ const ShaderInput* spaceInput = node.getInput(SPACE);
+ const int space = spaceInput ? spaceInput->getValue()->asA() : OBJECT_SPACE;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ if (space == WORLD_SPACE)
+ {
+ ShaderPort* tangent = vertexData[HW::T_TANGENT_WORLD];
+ if (!tangent->isEmitted())
+ {
+ tangent->setEmitted();
+ shadergen.emitLine(prefix + tangent->getVariable() + " = normalize((" + HW::T_WORLD_MATRIX + " * float4(" + HW::T_IN_TANGENT + ", 0.0)).xyz)", stage);
+ }
+ }
+ else
+ {
+ ShaderPort* tangent = vertexData[HW::T_TANGENT_OBJECT];
+ if (!tangent->isEmitted())
+ {
+ tangent->setEmitted();
+ shadergen.emitLine(prefix + tangent->getVariable() + " = " + HW::T_IN_TANGENT, stage);
+ }
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ if (space == WORLD_SPACE)
+ {
+ const ShaderPort* tangent = vertexData[HW::T_TANGENT_WORLD];
+ shadergen.emitString(" = normalize(" + prefix + tangent->getVariable() + ")", stage);
+ }
+ else
+ {
+ const ShaderPort* tangent = vertexData[HW::T_TANGENT_OBJECT];
+ shadergen.emitString(" = normalize(" + prefix + tangent->getVariable() + ")", stage);
+ }
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/TangentNodeMsl.h b/source/MaterialXGenMsl/Nodes/TangentNodeMsl.h
new file mode 100644
index 0000000000..4ba0c3d031
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TangentNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_TANGENTNODEMSL_H
+#define MATERIALX_TANGENTNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Tangent node implementation for MSL
+class MX_GENMSL_API TangentNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/TexCoordNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/TexCoordNodeMsl.cpp
new file mode 100644
index 0000000000..a88152eb82
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TexCoordNodeMsl.cpp
@@ -0,0 +1,62 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr TexCoordNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void TexCoordNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ const ShaderOutput* output = node.getOutput();
+ const ShaderInput* indexInput = node.getInput(INDEX);
+ const string index = indexInput ? indexInput->getValue()->getValueString() : "0";
+
+ ShaderStage& vs = shader.getStage(Stage::VERTEX);
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+
+ addStageInput(HW::VERTEX_INPUTS, output->getType(), HW::T_IN_TEXCOORD + "_" + index, vs);
+ addStageConnector(HW::VERTEX_DATA, output->getType(), HW::T_TEXCOORD + "_" + index, vs, ps);
+}
+
+void TexCoordNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ const ShaderInput* indexInput = node.getInput(INDEX);
+ const string index = indexInput ? indexInput->getValue()->getValueString() : "0";
+ const string variable = HW::T_TEXCOORD + "_" + index;
+
+ DEFINE_SHADER_STAGE(stage, Stage::VERTEX)
+ {
+ VariableBlock& vertexData = stage.getOutputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* texcoord = vertexData[variable];
+ if (!texcoord->isEmitted())
+ {
+ shadergen.emitLine(prefix + texcoord->getVariable() + " = " + HW::T_IN_TEXCOORD + "_" + index, stage);
+ texcoord->setEmitted();
+ }
+ }
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ VariableBlock& vertexData = stage.getInputBlock(HW::VERTEX_DATA);
+ const string prefix = shadergen.getVertexDataPrefix(vertexData);
+ ShaderPort* texcoord = vertexData[variable];
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = " + prefix + texcoord->getVariable(), stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/TexCoordNodeMsl.h b/source/MaterialXGenMsl/Nodes/TexCoordNodeMsl.h
new file mode 100644
index 0000000000..9d5778fe22
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TexCoordNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_TEXCOORDNODEMSL_H
+#define MATERIALX_TEXCOORDNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// TexCoord node implementation for MSL
+class MX_GENMSL_API TexCoordNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/TimeNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/TimeNodeMsl.cpp
new file mode 100644
index 0000000000..0c54861b40
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TimeNodeMsl.cpp
@@ -0,0 +1,38 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr TimeNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void TimeNodeMsl::createVariables(const ShaderNode&, GenContext&, Shader& shader) const
+{
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::FLOAT, HW::T_FRAME, ps);
+}
+
+void TimeNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = " + HW::T_FRAME + " / ", stage);
+ const ShaderInput* fpsInput = node.getInput("fps");
+ const string fps = fpsInput->getValue()->getValueString();
+ shadergen.emitString(fps, stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/TimeNodeMsl.h b/source/MaterialXGenMsl/Nodes/TimeNodeMsl.h
new file mode 100644
index 0000000000..aa7b392ae5
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TimeNodeMsl.h
@@ -0,0 +1,26 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_TIMENODEMSL_H
+#define MATERIALX_TIMENODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Time node implementation for MSL
+class MX_GENMSL_API TimeNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/TransformNormalNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/TransformNormalNodeMsl.cpp
new file mode 100644
index 0000000000..98810c025b
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TransformNormalNodeMsl.cpp
@@ -0,0 +1,43 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr TransformNormalNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void TransformNormalNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ TransformVectorNodeMsl::emitFunctionCall(node, context, stage);
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ const ShaderOutput* output = node.getOutput();
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(output, false, false, context, stage);
+ shadergen.emitString(" = normalize(" + output->getVariable() + ")", stage);
+ shadergen.emitLineEnd(stage);
+ }
+}
+
+const string& TransformNormalNodeMsl::getMatrix(const string& fromSpace, const string& toSpace) const
+{
+ if ((fromSpace == MODEL || fromSpace == OBJECT) && toSpace == WORLD)
+ {
+ return HW::T_WORLD_INVERSE_TRANSPOSE_MATRIX;
+ }
+ else if (fromSpace == WORLD && (toSpace == MODEL || toSpace == OBJECT))
+ {
+ return HW::T_WORLD_TRANSPOSE_MATRIX;
+ }
+ return EMPTY_STRING;
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/TransformNormalNodeMsl.h b/source/MaterialXGenMsl/Nodes/TransformNormalNodeMsl.h
new file mode 100644
index 0000000000..5242614670
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TransformNormalNodeMsl.h
@@ -0,0 +1,27 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_TRANSFORMNORMALNODEMSL_H
+#define MATERIALX_TRANSFORMNORMALNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// TransformNormal node implementation for MSL
+class MX_GENMSL_API TransformNormalNodeMsl : public TransformVectorNodeMsl
+{
+public:
+ static ShaderNodeImplPtr create();
+
+protected:
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+ const string& getMatrix(const string& fromSpace, const string& toSpace) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/TransformPointNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/TransformPointNodeMsl.cpp
new file mode 100644
index 0000000000..38db776855
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TransformPointNodeMsl.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr TransformPointNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+string TransformPointNodeMsl::getHomogeneousCoordinate(const ShaderInput* in, GenContext& context) const
+{
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ return "float4(" + shadergen.getUpstreamResult(in, context) + ", 1.0)";
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/TransformPointNodeMsl.h b/source/MaterialXGenMsl/Nodes/TransformPointNodeMsl.h
new file mode 100644
index 0000000000..c8f9007886
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TransformPointNodeMsl.h
@@ -0,0 +1,25 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_TRANSFORMPOINTNODEMSL_H
+#define MATERIALX_TRANSFORMPOINTNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// TransformPoint node implementation for MSL
+class MX_GENMSL_API TransformPointNodeMsl : public TransformVectorNodeMsl
+{
+public:
+ static ShaderNodeImplPtr create();
+
+protected:
+ virtual string getHomogeneousCoordinate(const ShaderInput* in, GenContext& context) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/TransformVectorNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/TransformVectorNodeMsl.cpp
new file mode 100644
index 0000000000..bd6d9d4c3c
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TransformVectorNodeMsl.cpp
@@ -0,0 +1,85 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr TransformVectorNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void TransformVectorNodeMsl::createVariables(const ShaderNode& node, GenContext&, Shader& shader) const
+{
+ const ShaderInput* toSpaceInput = node.getInput(TO_SPACE);
+ string toSpace = toSpaceInput ? toSpaceInput->getValue()->getValueString() : EMPTY_STRING;
+
+ const ShaderInput* fromSpaceInput = node.getInput(FROM_SPACE);
+ string fromSpace = fromSpaceInput ? fromSpaceInput->getValue()->getValueString() : EMPTY_STRING;
+
+ const string& matrix = getMatrix(fromSpace, toSpace);
+ if (!matrix.empty())
+ {
+ ShaderStage& ps = shader.getStage(Stage::PIXEL);
+ addStageUniform(HW::PRIVATE_UNIFORMS, Type::MATRIX44, matrix, ps);
+ }
+}
+
+void TransformVectorNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+
+ const ShaderInput* inInput = node.getInput("in");
+ if (inInput->getType() != Type::VECTOR3 && inInput->getType() != Type::VECTOR4)
+ {
+ throw ExceptionShaderGenError("Transform node must have 'in' type of vector3 or vector4.");
+ }
+
+ const ShaderInput* toSpaceInput = node.getInput(TO_SPACE);
+ string toSpace = toSpaceInput ? toSpaceInput->getValue()->getValueString() : EMPTY_STRING;
+
+ const ShaderInput* fromSpaceInput = node.getInput(FROM_SPACE);
+ string fromSpace = fromSpaceInput ? fromSpaceInput->getValue()->getValueString() : EMPTY_STRING;
+
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(node.getOutput(), true, false, context, stage);
+ shadergen.emitString(" = (", stage);
+ const string& matrix = getMatrix(fromSpace, toSpace);
+ if (!matrix.empty())
+ {
+ shadergen.emitString(matrix + " * ", stage);
+ }
+ shadergen.emitString(getHomogeneousCoordinate(inInput, context), stage);
+ shadergen.emitString(").xyz", stage);
+ shadergen.emitLineEnd(stage);
+
+ }
+}
+
+const string& TransformVectorNodeMsl::getMatrix(const string& fromSpace, const string& toSpace) const
+{
+ if ((fromSpace == MODEL || fromSpace == OBJECT) && toSpace == WORLD)
+ {
+ return HW::T_WORLD_MATRIX;
+ }
+ else if (fromSpace == WORLD && (toSpace == MODEL || toSpace == OBJECT))
+ {
+ return HW::T_WORLD_INVERSE_MATRIX;
+ }
+ return EMPTY_STRING;
+}
+
+string TransformVectorNodeMsl::getHomogeneousCoordinate(const ShaderInput* in, GenContext& context) const
+{
+ const ShaderGenerator& shadergen = context.getShaderGenerator();
+ return "float4(" + shadergen.getUpstreamResult(in, context) + ", 0.0)";
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/TransformVectorNodeMsl.h b/source/MaterialXGenMsl/Nodes/TransformVectorNodeMsl.h
new file mode 100644
index 0000000000..1724d0a4ff
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/TransformVectorNodeMsl.h
@@ -0,0 +1,30 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_TRANSFORMVECTORNODEMSL_H
+#define MATERIALX_TRANSFORMVECTORNODEMSL_H
+
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// TransformVector node implementation for MSL
+class MX_GENMSL_API TransformVectorNodeMsl : public MslImplementation
+{
+public:
+ static ShaderNodeImplPtr create();
+
+ void createVariables(const ShaderNode& node, GenContext& context, Shader& shader) const override;
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+
+protected:
+ virtual const string& getMatrix(const string& fromSpace, const string& toSpace) const;
+ virtual string getHomogeneousCoordinate(const ShaderInput* in, GenContext& context) const;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenMsl/Nodes/UnlitSurfaceNodeMsl.cpp b/source/MaterialXGenMsl/Nodes/UnlitSurfaceNodeMsl.cpp
new file mode 100644
index 0000000000..5482314fe9
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/UnlitSurfaceNodeMsl.cpp
@@ -0,0 +1,50 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+ShaderNodeImplPtr UnlitSurfaceNodeMsl::create()
+{
+ return std::make_shared();
+}
+
+void UnlitSurfaceNodeMsl::emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const
+{
+ const MslShaderGenerator& shadergen = static_cast(context.getShaderGenerator());
+
+ DEFINE_SHADER_STAGE(stage, Stage::PIXEL)
+ {
+
+ // Declare the output variable
+ const ShaderOutput* output = node.getOutput();
+ shadergen.emitLineBegin(stage);
+ shadergen.emitOutput(output, true, true, context, stage);
+ shadergen.emitLineEnd(stage);
+
+ const string outColor = output->getVariable() + ".color";
+ const string outTransparency = output->getVariable() + ".transparency";
+
+ const ShaderInput* emission = node.getInput("emission");
+ const ShaderInput* emissionColor = node.getInput("emission_color");
+ shadergen.emitLine(outColor + " = " + shadergen.getUpstreamResult(emission, context) + " * " + shadergen.getUpstreamResult(emissionColor, context), stage);
+
+ const ShaderInput* transmission = node.getInput("transmission");
+ const ShaderInput* transmissionColor = node.getInput("transmission_color");
+ shadergen.emitLine(outTransparency + " = " + shadergen.getUpstreamResult(transmission, context) + " * " + shadergen.getUpstreamResult(transmissionColor, context), stage);
+
+ const ShaderInput* opacity = node.getInput("opacity");
+ const string surfaceOpacity = shadergen.getUpstreamResult(opacity, context);
+ shadergen.emitLine(outColor + " *= " + surfaceOpacity, stage);
+ shadergen.emitLine(outTransparency + " = mix(float3(1.0), " + outTransparency + ", " + surfaceOpacity + ")", stage);
+
+ }
+}
+
+MATERIALX_NAMESPACE_END
diff --git a/source/MaterialXGenMsl/Nodes/UnlitSurfaceNodeMsl.h b/source/MaterialXGenMsl/Nodes/UnlitSurfaceNodeMsl.h
new file mode 100644
index 0000000000..8e9693a2f4
--- /dev/null
+++ b/source/MaterialXGenMsl/Nodes/UnlitSurfaceNodeMsl.h
@@ -0,0 +1,25 @@
+//
+// Copyright Contributors to the MaterialX Project
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef MATERIALX_UNLITSURFACENODEMSL_H
+#define MATERIALX_UNLITSURFACENODEMSL_H
+
+#include
+#include
+
+MATERIALX_NAMESPACE_BEGIN
+
+/// Unlit surface node implementation for MSL
+class MX_GENMSL_API UnlitSurfaceNodeMsl : public MslImplementation
+{
+ public:
+ static ShaderNodeImplPtr create();
+
+ void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) const override;
+};
+
+MATERIALX_NAMESPACE_END
+
+#endif
diff --git a/source/MaterialXGenShader/ShaderGenerator.cpp b/source/MaterialXGenShader/ShaderGenerator.cpp
index 6445010e73..ecfc8645b2 100644
--- a/source/MaterialXGenShader/ShaderGenerator.cpp
+++ b/source/MaterialXGenShader/ShaderGenerator.cpp
@@ -273,6 +273,14 @@ void ShaderGenerator::registerImplementation(const string& name, CreatorFunction
_implFactory.registerClass(name, creator);
}
+void ShaderGenerator::registerImplementation(const StringVec& nameVec, CreatorFunction