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 creator) +{ + for(const string& name : nameVec) + { + _implFactory.registerClass(name, creator); + } +} + bool ShaderGenerator::implementationRegistered(const string& name) const { return _implFactory.classRegistered(name); diff --git a/source/MaterialXGenShader/ShaderGenerator.h b/source/MaterialXGenShader/ShaderGenerator.h index efa8cbe843..7045f43b73 100644 --- a/source/MaterialXGenShader/ShaderGenerator.h +++ b/source/MaterialXGenShader/ShaderGenerator.h @@ -150,6 +150,9 @@ class MX_GENSHADER_API ShaderGenerator /// Register a shader node implementation for a given implementation element name void registerImplementation(const string& name, CreatorFunction creator); + + /// Register a shader node implementation for a given set of implementation element names + void registerImplementation(const StringVec& nameVec, CreatorFunction creator); /// Determine if a shader node implementation has been registered for a given implementation element name bool implementationRegistered(const string& name) const; diff --git a/source/MaterialXGenShader/Syntax.h b/source/MaterialXGenShader/Syntax.h index c0f17da342..1dec9419b5 100644 --- a/source/MaterialXGenShader/Syntax.h +++ b/source/MaterialXGenShader/Syntax.h @@ -86,7 +86,7 @@ class MX_GENSHADER_API Syntax const string& getTypeName(const TypeDesc* type) const; /// Returns the type name in an output context - string getOutputTypeName(const TypeDesc* type) const; + virtual string getOutputTypeName(const TypeDesc* type) const; /// Returns a type alias for the given data type. /// If not used returns an empty string. diff --git a/source/MaterialXRender/CMakeLists.txt b/source/MaterialXRender/CMakeLists.txt index 0033792928..63ebe9068c 100644 --- a/source/MaterialXRender/CMakeLists.txt +++ b/source/MaterialXRender/CMakeLists.txt @@ -3,6 +3,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../) file(GLOB_RECURSE materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB_RECURSE materialx_inlined "${CMAKE_CURRENT_SOURCE_DIR}/*.inl") file(GLOB_RECURSE materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*") if(NOT MATERIALX_BUILD_OIIO) @@ -10,13 +11,14 @@ if(NOT MATERIALX_BUILD_OIIO) endif() assign_source_group("Source Files" ${materialx_source}) +assign_source_group("Source Files" ${materialx_inlined}) assign_source_group("Header Files" ${materialx_headers}) if(UNIX) add_compile_options(-Wno-unused-function) endif() -add_library(MaterialXRender ${materialx_source} ${materialx_headers}) +add_library(MaterialXRender ${materialx_source} ${materialx_headers} ${materialx_inlined}) add_definitions(-DMATERIALX_RENDER_EXPORTS) diff --git a/source/MaterialXRender/Camera.cpp b/source/MaterialXRender/Camera.cpp index ad56136611..6464246e3a 100644 --- a/source/MaterialXRender/Camera.cpp +++ b/source/MaterialXRender/Camera.cpp @@ -22,6 +22,28 @@ Matrix44 Camera::createViewMatrix(const Vector3& eye, -x.dot(eye), -y.dot(eye), z.dot(eye), 1.0f); } +Matrix44 Camera::createPerspectiveMatrixZP(float left, float right, + float bottom, float top, + float nearP, float farP) +{ + return Matrix44( + (2.0f * nearP) / (right - left), 0.0f, (right + left) / (right - left), 0.0f, + 0.0f, (2.0f * nearP) / (top - bottom), (top + bottom) / (top - bottom), 0.0f, + 0.0f, 0.0f, -1 / (farP - nearP), -1.0f, + 0.0f, 0.0f, -nearP / (farP - nearP), 0.0f); +} + +Matrix44 Camera::createOrthographicMatrixZP(float left, float right, + float bottom, float top, + float nearP, float farP) +{ + return Matrix44( + 2.0f / (right - left), 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / (top - bottom), 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f / (farP - nearP), 0.0f, + -(right + left) / (right - left), -(top + bottom) / (top - bottom), -nearP / (farP - nearP), 1.0f); +} + Matrix44 Camera::createPerspectiveMatrix(float left, float right, float bottom, float top, float nearP, float farP) diff --git a/source/MaterialXRender/Camera.h b/source/MaterialXRender/Camera.h index f2b3357680..a20c0f0e70 100644 --- a/source/MaterialXRender/Camera.h +++ b/source/MaterialXRender/Camera.h @@ -154,15 +154,25 @@ class MX_RENDER_API Camera const Vector3& target, const Vector3& up); - /// Create a perpective projection matrix given a set of clip planes. + /// Create a perpective projection matrix given a set of clip planes with [-1,1] projected Z. static Matrix44 createPerspectiveMatrix(float left, float right, float bottom, float top, float nearP, float farP); - /// Create an orthographic projection matrix given a set of clip planes. + /// Create an orthographic projection matrix given a set of clip planes with [-1,1] projected Z. static Matrix44 createOrthographicMatrix(float left, float right, float bottom, float top, float nearP, float farP); + + /// Create a perpective projection matrix given a set of clip planes with [0,1] projected Z. + static Matrix44 createPerspectiveMatrixZP(float left, float right, + float bottom, float top, + float nearP, float farP); + + /// Create an orthographic projection matrix given a set of clip planes with [0,1] projected Z. + static Matrix44 createOrthographicMatrixZP(float left, float right, + float bottom, float top, + float nearP, float farP); /// Apply a perspective transform to the given 3D point, performing a /// homogeneous divide on the transformed result. diff --git a/source/MaterialXRender/GeometryHandler.cpp b/source/MaterialXRender/GeometryHandler.cpp index 7feca6b95a..e2c974a767 100644 --- a/source/MaterialXRender/GeometryHandler.cpp +++ b/source/MaterialXRender/GeometryHandler.cpp @@ -128,7 +128,7 @@ MeshPtr GeometryHandler::findParentMesh(MeshPartitionPtr part) return nullptr; } -MeshPtr GeometryHandler::createQuadMesh() +MeshPtr GeometryHandler::createQuadMesh(const Vector2& uvMin, const Vector2& uvMax, bool flipTexCoordsHorizontally) { MeshStreamPtr quadPositions = MeshStream::create(HW::IN_POSITION, MeshStream::POSITION_ATTRIBUTE, 0); quadPositions->setStride(MeshStream::STRIDE_3D); @@ -138,10 +138,22 @@ MeshPtr GeometryHandler::createQuadMesh() -1.0f, 1.0f, 0.0f }); MeshStreamPtr quadTexCoords = MeshStream::create(HW::IN_TEXCOORD + "_0", MeshStream::TEXCOORD_ATTRIBUTE, 0); quadTexCoords->setStride(MeshStream::STRIDE_2D); - quadTexCoords->getData().assign({ 1.0f, 1.0f, - 1.0f, 0.0f, - 0.0f, 0.0f, - 0.0f, 1.0f }); + if (!flipTexCoordsHorizontally) + { + quadTexCoords->getData().assign({ + uvMax[0], uvMax[1], + uvMax[0], uvMin[1], + uvMin[0], uvMin[1], + uvMin[0], uvMax[1] }); + } + else + { + quadTexCoords->getData().assign({ + uvMax[0], uvMin[1], + uvMax[0], uvMax[1], + uvMin[0], uvMax[1], + uvMin[0], uvMin[1] }); + } MeshPartitionPtr quadIndices = MeshPartition::create(); quadIndices->getIndices().assign({ 0, 1, 3, 1, 2, 3 }); quadIndices->setFaceCount(6); diff --git a/source/MaterialXRender/GeometryHandler.h b/source/MaterialXRender/GeometryHandler.h index 292308053a..0022487602 100644 --- a/source/MaterialXRender/GeometryHandler.h +++ b/source/MaterialXRender/GeometryHandler.h @@ -118,7 +118,9 @@ class MX_RENDER_API GeometryHandler } /// Utility to create a quad mesh - static MeshPtr createQuadMesh(); + static MeshPtr createQuadMesh(const Vector2& uvMin = Vector2(0.0f, 0.0f), + const Vector2& uvMax = Vector2(1.0f, 1.0f), + bool flipTexCoordsHorizontally = false); protected: // Recompute bounds for all stored geometry diff --git a/source/MaterialXRender/Image.h b/source/MaterialXRender/Image.h index 64e5284eb9..f69983ca73 100644 --- a/source/MaterialXRender/Image.h +++ b/source/MaterialXRender/Image.h @@ -214,7 +214,7 @@ class MX_RENDER_API Image void* _resourceBuffer; ImageBufferDeallocator _resourceBufferDeallocator; - unsigned int _resourceId; + unsigned int _resourceId = 0; }; /// Create a uniform-color image with the given properties. diff --git a/source/MaterialXRender/ImageHandler.cpp b/source/MaterialXRender/ImageHandler.cpp index be49f9eeba..a15b17701e 100644 --- a/source/MaterialXRender/ImageHandler.cpp +++ b/source/MaterialXRender/ImageHandler.cpp @@ -312,4 +312,14 @@ void ImageSamplingProperties::setProperties(const string& fileNameUniform, } } +bool ImageSamplingProperties::operator==(const ImageSamplingProperties& r) const +{ + return + (enableMipmaps == r.enableMipmaps && + uaddressMode == r.uaddressMode && + vaddressMode == r.vaddressMode && + filterType == r.filterType && + defaultColor == r.defaultColor) ; +} + MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRender/ImageHandler.h b/source/MaterialXRender/ImageHandler.h index 27291d1833..a60aaa9add 100644 --- a/source/MaterialXRender/ImageHandler.h +++ b/source/MaterialXRender/ImageHandler.h @@ -48,6 +48,8 @@ class MX_RENDER_API ImageSamplingProperties /// @param uniformBlock Block containing sampler uniforms void setProperties(const string& fileNameUniform, const VariableBlock& uniformBlock); + + bool operator==(const ImageSamplingProperties& r) const; /// Address mode options. Matches enumerations allowed for image address /// modes, except UNSPECIFIED which indicates no explicit mode was defined. @@ -86,6 +88,20 @@ class MX_RENDER_API ImageSamplingProperties Color4 defaultColor = { 0.0f, 0.0f, 0.0f, 1.0f }; }; +/// @struct ImageSamplingKeyHasher +/// Class used for hashing ImageSamplingProperties in an unordered_map +struct MX_RENDER_API ImageSamplingKeyHasher +{ + size_t operator()(const ImageSamplingProperties& k) const + { + return (size_t) k.enableMipmaps + // 1 bit + (((size_t) k.filterType & 3) << 1) + // 2 bit + ((((size_t) k.uaddressMode + 1) & 7) << 3) + // 3 bit + ((((size_t) k.vaddressMode + 1) & 7) << 6) + // 3 bit + ((((size_t) k.defaultColor[0] + 1)) << 9) ; + } +}; + /// @class ImageLoader /// Abstract base class for file-system image loaders class MX_RENDER_API ImageLoader diff --git a/source/MaterialXRender/ShaderMaterial.cpp b/source/MaterialXRender/ShaderMaterial.cpp new file mode 100644 index 0000000000..d51efa54af --- /dev/null +++ b/source/MaterialXRender/ShaderMaterial.cpp @@ -0,0 +1,103 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +MATERIALX_NAMESPACE_BEGIN + +ShaderMaterial::ShaderMaterial() : _hasTransparency(false) {} +ShaderMaterial::~ShaderMaterial() {} + +void ShaderMaterial::setDocument(DocumentPtr doc) +{ + _doc = doc; +} + +DocumentPtr ShaderMaterial::getDocument() const +{ + return _doc; +} + +void ShaderMaterial::setElement(TypedElementPtr val) +{ + _elem = val; +} + +TypedElementPtr ShaderMaterial::getElement() const +{ + return _elem; +} + +void ShaderMaterial::setMaterialNode(NodePtr node) +{ + _materialNode = node; +} + +NodePtr ShaderMaterial::getMaterialNode() const +{ + return _materialNode; +} + +void ShaderMaterial::setUdim(const std::string& val) +{ + _udim = val; +} + +const std::string& ShaderMaterial::getUdim() +{ + return _udim; +} + +ShaderPtr ShaderMaterial::getShader() const +{ + return _hwShader; +} + +bool ShaderMaterial::hasTransparency() const +{ + return _hasTransparency; +} + +bool ShaderMaterial::generateEnvironmentShader(GenContext& context, + const FilePath& filename, + DocumentPtr stdLib, + const FilePath& imagePath) +{ + // Read in the environment nodegraph. + DocumentPtr doc = createDocument(); + doc->importLibrary(stdLib); + DocumentPtr envDoc = createDocument(); + readFromXmlFile(envDoc, filename); + doc->importLibrary(envDoc); + + NodeGraphPtr envGraph = doc->getNodeGraph("environmentDraw"); + if (!envGraph) + { + return false; + } + NodePtr image = envGraph->getNode("envImage"); + if (!image) + { + return false; + } + image->setInputValue("file", imagePath.asString(), FILENAME_TYPE_STRING); + OutputPtr output = envGraph->getOutput("out"); + if (!output) + { + return false; + } + + // Create the shader. + std::string shaderName = "__ENV_SHADER__"; + _hwShader = createShader(shaderName, context, output); + if (!_hwShader) + { + return false; + } + return generateShader(_hwShader); +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRender/ShaderMaterial.h b/source/MaterialXRender/ShaderMaterial.h new file mode 100644 index 0000000000..57f50eda75 --- /dev/null +++ b/source/MaterialXRender/ShaderMaterial.h @@ -0,0 +1,167 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_RENDER_SHADER_MATERIAL_H +#define MATERIALX_RENDER_SHADER_MATERIAL_H + +/// @file +/// ShaderMaterial helper classes + +#include +#include +#include +#include +#include +#include +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +using MaterialPtr = std::shared_ptr; + +/// @class ShadowState +/// Helper class representing shadow rendering state +class MX_RENDER_API ShadowState +{ + public: + ImagePtr shadowMap; + Matrix44 shadowMatrix; + ImagePtr ambientOcclusionMap; + float ambientOcclusionGain = 0.0f; +}; + +/// @class ShaderMaterial +/// Abstract class for shader generation and rendering of a ShaderMaterial +class MX_RENDER_API ShaderMaterial +{ + public: + ShaderMaterial(); + virtual ~ShaderMaterial(); + + /// Set the renderable element associated with this ShaderMaterial + void setDocument(DocumentPtr doc); + + /// Return the document associated with this ShaderMaterial + DocumentPtr getDocument() const; + + /// Set the renderable element associated with this ShaderMaterial + void setElement(TypedElementPtr val); + + /// Return the renderable element associated with this ShaderMaterial + TypedElementPtr getElement() const; + + /// Set the ShaderMaterial node associated with this ShaderMaterial + void setMaterialNode(NodePtr node); + + /// Return the ShaderMaterial node associated with this ShaderMaterial + NodePtr getMaterialNode() const; + + /// Set udim identifier + void setUdim(const std::string& val); + + /// Get any associated udim identifier + const std::string& getUdim(); + + /// Load shader source from file. + virtual bool loadSource(const FilePath& vertexShaderFile, + const FilePath& pixelShaderFile, + bool hasTransparency) = 0; + + /// Generate a shader from our currently stored element and + /// the given generator context. + virtual bool generateShader(GenContext& context) = 0; + + /// Copies shader and API specific generated program from ShaderMaterial to this one. + virtual void copyShader(MaterialPtr ShaderMaterial) = 0; + + /// Generate a shader from the given hardware shader. + virtual bool generateShader(ShaderPtr hwShader) = 0; + + /// Generate an environment background shader + virtual bool generateEnvironmentShader(GenContext& context, + const FilePath& filename, + DocumentPtr stdLib, + const FilePath& imagePath); + + /// Return the underlying hardware shader. + ShaderPtr getShader() const; + + /// Return true if this ShaderMaterial has transparency. + bool hasTransparency() const; + + /// Bind shader + virtual bool bindShader() const = 0; + + /// Bind viewing information for this ShaderMaterial. + virtual void bindViewInformation(CameraPtr camera) = 0; + + /// Bind all images for this ShaderMaterial. + virtual void bindImages(ImageHandlerPtr imageHandler, + const FileSearchPath& searchPath, + bool enableMipmaps = true) = 0; + + /// Unbbind all images for this ShaderMaterial. + virtual void unbindImages(ImageHandlerPtr imageHandler) = 0; + + /// Bind a single image. + virtual ImagePtr bindImage(const FilePath& filePath, + const std::string& uniformName, + ImageHandlerPtr imageHandler, + const ImageSamplingProperties& samplingProperties) = 0; + + /// Bind lights to shader. + virtual void bindLighting(LightHandlerPtr lightHandler, + ImageHandlerPtr imageHandler, + const ShadowState& shadowState) = 0; + + /// Bind units. + virtual void bindUnits(UnitConverterRegistryPtr& registry, + const GenContext& context) = 0; + + /// Bind the given mesh to this ShaderMaterial. + virtual void bindMesh(MeshPtr mesh) = 0; + + /// Bind a mesh partition to this ShaderMaterial. + virtual bool bindPartition(MeshPartitionPtr part) const = 0; + + /// Draw the given mesh partition. + virtual void drawPartition(MeshPartitionPtr part) const = 0; + + /// Unbind all geometry from this ShaderMaterial. + virtual void unbindGeometry() = 0; + + /// Return the block of public uniforms for this ShaderMaterial. + virtual VariableBlock* getPublicUniforms() const = 0; + + /// Find a public uniform from its MaterialX path. + virtual ShaderPort* findUniform(const std::string& path) const = 0; + + /// Modify the value of the uniform with the given path. + virtual void modifyUniform(const std::string& path, + ConstValuePtr value, + std::string valueString = EMPTY_STRING) = 0; + + protected: + virtual void clearShader() = 0; + + protected: + ShaderPtr _hwShader; + MeshPtr _boundMesh; + + DocumentPtr _doc; + TypedElementPtr _elem; + NodePtr _materialNode; + + std::string _udim; + bool _hasTransparency; + + ImageVec _boundImages; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRender/TextureBaker.h b/source/MaterialXRender/TextureBaker.h new file mode 100644 index 0000000000..510953d502 --- /dev/null +++ b/source/MaterialXRender/TextureBaker.h @@ -0,0 +1,299 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_TEXTUREBAKER +#define MATERIALX_TEXTUREBAKER + +/// @file +/// Texture baking functionality + +#include + +#include + +#include +#include +#include +#include + +MATERIALX_NAMESPACE_BEGIN + +/// A vector of baked documents with their associated names. +using BakedDocumentVec = std::vector>; + +/// @class TextureBaker +/// A helper class for baking procedural material content to textures. +/// TODO: Add support for graphs containing geometric nodes such as position +/// and normal. +template +class TextureBaker : public Renderer +{ + public: + /// Set the file extension for baked textures. + void setExtension(const string& extension) + { + _extension = extension; + } + + /// Return the file extension for baked textures. + const string& getExtension() const + { + return _extension; + } + + /// Set the color space in which color textures are encoded. + /// + /// By default, this color space is srgb_texture, and color inputs are + /// automatically transformed to this space by the baker. If another color + /// space is set, then the input graph is responsible for transforming + /// colors to this space. + void setColorSpace(const string& colorSpace) + { + _colorSpace = colorSpace; + } + + /// Return the color space in which color textures are encoded. + const string& getColorSpace() const + { + return _colorSpace; + } + + /// Set the distance unit to which textures are baked. Defaults to meters. + void setDistanceUnit(const string& unitSpace) + { + _distanceUnit = unitSpace; + } + + /// Return the distance unit to which textures are baked. + const string& getDistanceUnit() const + { + return _distanceUnit; + } + + /// Set whether images should be averaged to generate constants. Defaults to false. + void setAverageImages(bool enable) + { + _averageImages = enable; + } + + /// Return whether images should be averaged to generate constants. + bool getAverageImages() const + { + return _averageImages; + } + + /// Set whether uniform textures should be stored as constants. Defaults to true. + void setOptimizeConstants(bool enable) + { + _optimizeConstants = enable; + } + + /// Return whether uniform textures should be stored as constants. + bool getOptimizeConstants() const + { + return _optimizeConstants; + } + + /// Set the output location for baked texture images. Defaults to the root folder + /// of the destination material. + void setOutputImagePath(const FilePath& outputImagePath) + { + _outputImagePath = outputImagePath; + } + + /// Get the current output location for baked texture images. + const FilePath& getOutputImagePath() + { + return _outputImagePath; + } + + /// Set the name of the baked graph element. + void setBakedGraphName(const string& name) + { + _bakedGraphName= name; + } + + /// Return the name of the baked graph element. + const string& getBakedGraphName() const + { + return _bakedGraphName; + } + + /// Set the name of the baked geometry info element. + void setBakedGeomInfoName(const string& name) + { + _bakedGeomInfoName = name; + } + + /// Return the name of the baked geometry info element. + const string& getBakedGeomInfoName() const + { + return _bakedGeomInfoName; + } + + /// Get the texture filename template. + const string& getTextureFilenameTemplate() const + { + return _textureFilenameTemplate; + } + + /// Set the texture filename template. + void setTextureFilenameTemplate(const string& filenameTemplate) + { + _textureFilenameTemplate = (filenameTemplate.find("$EXTENSION") == string::npos) ? + filenameTemplate + ".$EXTENSION" : filenameTemplate; + } + + /// Set texFilenameOverrides if template variable exists. + void setFilenameTemplateVarOverride(const string& key, const string& value) + { + if (_permittedOverrides.count(key)) + { + _texTemplateOverrides[key] = value; + } + } + + /// Set the output stream for reporting progress and warnings. Defaults to std::cout. + void setOutputStream(std::ostream* outputStream) + { + _outputStream = outputStream; + } + + /// Return the output stream for reporting progress and warnings. + std::ostream* getOutputStream() const + { + return _outputStream; + } + + /// Set whether to create a short name for baked images by hashing the baked image filenames + /// This is useful for file systems which may have a maximum limit on filename size. + /// By default names are not hashed. + void setHashImageNames(bool enable) + { + _hashImageNames = enable; + } + + /// Return whether automatic baked texture resolution is set. + bool getHashImageNames() const + { + return _hashImageNames; + } + + /// Set the minimum texcoords used in texture baking. Defaults to 0, 0. + void setTextureSpaceMin(const Vector2& min) + { + _textureSpaceMin = min; + } + + /// Return the minimum texcoords used in texture baking. + Vector2 getTextureSpaceMin() const + { + return _textureSpaceMin; + } + + /// Set the maximum texcoords used in texture baking. Defaults to 1, 1. + void setTextureSpaceMax(const Vector2& max) + { + _textureSpaceMax = max; + } + + /// Return the maximum texcoords used in texture baking. + Vector2 getTextureSpaceMax() const + { + return _textureSpaceMax; + } + + /// Set up the unit definitions to be used in baking. + void setupUnitSystem(DocumentPtr unitDefinitions); + + /// Bake textures for all graph inputs of the given shader. + void bakeShaderInputs(NodePtr material, NodePtr shader, GenContext& context, const string& udim = EMPTY_STRING); + + /// Bake a texture for the given graph output. + void bakeGraphOutput(OutputPtr output, GenContext& context, const StringMap& filenameTemplateMap); + + /// Optimize baked textures before writing. + void optimizeBakedTextures(NodePtr shader); + + /// Bake material to document in memory and write baked textures to disk. + DocumentPtr bakeMaterialToDoc(DocumentPtr doc, const FileSearchPath& searchPath, const string& materialPath, + const StringVec& udimSet, std::string& documentName); + + /// Bake materials in the given document and write them to disk. If multiple documents are written, + /// then the given output filename will be used as a template. + void bakeAllMaterials(DocumentPtr doc, const FileSearchPath& searchPath, const FilePath& outputFileName); + + string getValueStringFromColor(const Color4& color, const string& type); + + protected: + class BakedImage + { + public: + FilePath filename; + Color4 uniformColor; + bool isUniform = false; + }; + class BakedConstant + { + public: + Color4 color; + bool isDefault = false; + }; + using BakedImageVec = vector; + using BakedImageMap = std::unordered_map; + using BakedConstantMap = std::unordered_map; + + protected: + TextureBaker(unsigned int width, unsigned int height, Image::BaseType baseType, bool flipSavedImage); + + // Populate file template variable naming map + StringMap initializeFileTemplateMap(InputPtr input, NodePtr shader, const string& udim = EMPTY_STRING); + + // Find first occurence of variable in filename from start index onwards + size_t findVarInTemplate(const string& filename, const string& var, size_t start = 0); + + // Generate a texture filename for the given graph output. + FilePath generateTextureFilename(const StringMap& fileTemplateMap); + + // Create document that links shader outputs to a material. + DocumentPtr generateNewDocumentFromShader(NodePtr shader, const StringVec& udimSet); + + // Write a baked image to disk, returning true if the write was successful. + bool writeBakedImage(const BakedImage& baked, ImagePtr image); + + protected: + string _extension; + string _colorSpace; + string _distanceUnit; + bool _averageImages; + bool _optimizeConstants; + FilePath _outputImagePath; + string _bakedGraphName; + string _bakedGeomInfoName; + string _textureFilenameTemplate; + std::ostream* _outputStream; + bool _hashImageNames; + Vector2 _textureSpaceMin; + Vector2 _textureSpaceMax; + + ShaderGeneratorPtr _generator; + ConstNodePtr _material; + ImagePtr _frameCaptureImage; + BakedImageMap _bakedImageMap; + BakedConstantMap _bakedConstantMap; + StringSet _permittedOverrides; + StringMap _texTemplateOverrides; + StringMap _bakedInputMap; + + std::unordered_map _worldSpaceNodes; + + bool _flipSavedImage; +}; + +MATERIALX_NAMESPACE_END + +#include + +#endif diff --git a/source/MaterialXRender/TextureBaker.inl b/source/MaterialXRender/TextureBaker.inl new file mode 100644 index 0000000000..54f83d1dba --- /dev/null +++ b/source/MaterialXRender/TextureBaker.inl @@ -0,0 +1,638 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +namespace { + +const string SRGB_TEXTURE = "srgb_texture"; +const string LIN_REC709 = "lin_rec709"; +const string BAKED_POSTFIX = "_baked"; +const string SHADER_PREFIX = "SR_"; +const string DEFAULT_UDIM_PREFIX = "_"; + +} // anonymous namespace + +template +string TextureBaker::getValueStringFromColor(const Color4& color, const string& type) +{ + if (type == "color4" || type == "vector4") + { + return toValueString(color); + } + if (type == "color3" || type == "vector3") + { + return toValueString(Vector3(color[0], color[1], color[2])); + } + if (type == "vector2") + { + return toValueString(Vector2(color[0], color[1])); + } + if (type == "float") + { + return toValueString(color[0]); + } + return EMPTY_STRING; +} + +template +TextureBaker::TextureBaker(unsigned int width, unsigned int height, Image::BaseType baseType, bool flipSavedImage) : + Renderer(width, height, baseType), + _distanceUnit("meter"), + _averageImages(false), + _optimizeConstants(true), + _bakedGraphName("NG_baked"), + _bakedGeomInfoName("GI_baked"), + _textureFilenameTemplate("$MATERIAL_$SHADINGMODEL_$INPUT$UDIMPREFIX$UDIM.$EXTENSION"), + _outputStream(&std::cout), + _hashImageNames(false), + _textureSpaceMin(0.0f), + _textureSpaceMax(1.0f), + _generator(ShaderGen::create()), + _permittedOverrides({ "$ASSET", "$MATERIAL", "$UDIMPREFIX" }), + _flipSavedImage(flipSavedImage) +{ + if (baseType == Image::BaseType::UINT8) + { +#if MATERIALX_BUILD_OIIO + _extension = ImageLoader::TIFF_EXTENSION; +#else + _extension = ImageLoader::PNG_EXTENSION; +#endif + _colorSpace = SRGB_TEXTURE; + } + else + { +#if MATERIALX_BUILD_OIIO + _extension = ImageLoader::EXR_EXTENSION; +#else + _extension = ImageLoader::HDR_EXTENSION; +#endif + _colorSpace = LIN_REC709; + } + + // Initialize our base renderer. + Renderer::initialize(); + + // Initialize our image handler. + Renderer::_imageHandler = Renderer::createImageHandler(StbImageLoader::create()); +#if MATERIALX_BUILD_OIIO + Renderer::_imageHandler->addLoader(OiioImageLoader::create()); +#endif + + // Create our dedicated frame capture image. + _frameCaptureImage = Image::create(width, height, 4, baseType); + _frameCaptureImage->createResourceBuffer(); +} + +template +size_t TextureBaker::findVarInTemplate(const string& filename, const string& var, size_t start) +{ + size_t i = filename.find(var, start); + if (var == "$UDIM" && i != string::npos) + { + size_t udimPrefix = filename.find("$UDIMPREFIX", start); + if (i == udimPrefix) + { + i = filename.find(var, i + 1); + } + } + return i; +} + +template +FilePath TextureBaker::generateTextureFilename(const StringMap& filenameTemplateMap) +{ + string bakedImageName = _textureFilenameTemplate; + + for (auto& pair : filenameTemplateMap) + { + string replacement = (_texTemplateOverrides.count(pair.first)) ? + _texTemplateOverrides[pair.first] : pair.second; + replacement = (filenameTemplateMap.at("$UDIM").empty() && pair.first == "$UDIMPREFIX") ? + EMPTY_STRING : replacement; + + for (size_t i = 0; (i = findVarInTemplate(bakedImageName, pair.first, i)) != string::npos; i++) + { + bakedImageName.replace(i, pair.first.length(), replacement); + } + } + + if (_hashImageNames) + { + std::stringstream hashStream; + hashStream << std::hash{}(bakedImageName); + hashStream << "." + getExtension(); + bakedImageName = hashStream.str(); + } + return _outputImagePath / bakedImageName; +} + +template +StringMap TextureBaker::initializeFileTemplateMap(InputPtr input, NodePtr shader, const string& udim) +{ + FilePath assetPath = FilePath(shader->getActiveSourceUri()); + assetPath.removeExtension(); + StringMap filenameTemplateMap; + filenameTemplateMap["$ASSET"] = assetPath.getBaseName(); + filenameTemplateMap["$INPUT"] = _bakedInputMap[input->getName()]; + filenameTemplateMap["$EXTENSION"] = _extension; + filenameTemplateMap["$MATERIAL"] = _material->getName(); + filenameTemplateMap["$SHADINGMODEL"] = shader->getCategory(); + filenameTemplateMap["$UDIM"] = udim; + filenameTemplateMap["$UDIMPREFIX"] = DEFAULT_UDIM_PREFIX; + return filenameTemplateMap; +} + +template +bool TextureBaker::writeBakedImage(const BakedImage& baked, ImagePtr image) +{ + if (!Renderer::_imageHandler->saveImage(baked.filename, image, _flipSavedImage)) + { + if (_outputStream) + { + *_outputStream << "Failed to write baked image: " << baked.filename.asString() << std::endl; + } + return false; + } + + if (_outputStream) + { + *_outputStream << "Wrote baked image: " << baked.filename.asString() << std::endl; + } + + return true; +} + +template +void TextureBaker::bakeShaderInputs(NodePtr material, NodePtr shader, GenContext& context, const string& udim) +{ + _material = material; + + if (!shader) + { + return; + } + + std::unordered_map bakedOutputMap; + for (InputPtr input : shader->getInputs()) + { + OutputPtr output = input->getConnectedOutput(); + if (output && !bakedOutputMap.count(output)) + { + bakedOutputMap[output] = input; + _bakedInputMap[input->getName()] = input->getName(); + + // When possible, nodes with world-space outputs are applied outside of the baking process. + NodePtr worldSpaceNode = connectsToWorldSpaceNode(output); + if (worldSpaceNode) + { + output->setConnectedNode(worldSpaceNode->getConnectedNode("in")); + _worldSpaceNodes[input->getName()] = worldSpaceNode; + } + StringMap filenameTemplateMap = initializeFileTemplateMap(input, shader, udim); + bakeGraphOutput(output, context, filenameTemplateMap); + } + else if (bakedOutputMap.count(output)) + { + // When the input shares the same output as a previously baked input, we use the already baked input. + _bakedInputMap[input->getName()] = bakedOutputMap[output]->getName(); + } + } + + // Release all images used to generate this set of shader inputs. + Renderer::_imageHandler->clearImageCache(); +} + +template +void TextureBaker::bakeGraphOutput(OutputPtr output, GenContext& context, const StringMap& filenameTemplateMap) +{ + if (!output) + { + return; + } + + bool encodeSrgb = _colorSpace == SRGB_TEXTURE && + (output->getType() == "color3" || output->getType() == "color4"); + Renderer::getFramebuffer()->setEncodeSrgb(encodeSrgb); + + ShaderPtr shader = _generator->generate("BakingShader", output, context); + Renderer::createProgram(shader); + + // Render and capture the requested image. + Renderer::renderTextureSpace(getTextureSpaceMin(), getTextureSpaceMax()); + string texturefilepath = generateTextureFilename(filenameTemplateMap); + Renderer::captureImage(_frameCaptureImage); + + // Construct a baked image record. + BakedImage baked; + baked.filename = texturefilepath; + if (_averageImages) + { + baked.uniformColor = _frameCaptureImage->getAverageColor(); + baked.isUniform = true; + } + else if (_frameCaptureImage->isUniformColor(&baked.uniformColor)) + { + baked.isUniform = true; + } + _bakedImageMap[output].push_back(baked); + + // TODO: Write images to memory rather than to disk. + // Write non-uniform images to disk. + if (!baked.isUniform) + { + writeBakedImage(baked, _frameCaptureImage); + } +} + +template +void TextureBaker::optimizeBakedTextures(NodePtr shader) +{ + if (!shader) + { + return; + } + + // Check for fully uniform outputs. + for (auto& pair : _bakedImageMap) + { + bool outputIsUniform = true; + for (BakedImage& baked : pair.second) + { + if (!baked.isUniform || baked.uniformColor != pair.second[0].uniformColor) + { + outputIsUniform = false; + continue; + } + } + if (outputIsUniform) + { + BakedConstant bakedConstant; + bakedConstant.color = pair.second[0].uniformColor; + _bakedConstantMap[pair.first] = bakedConstant; + } + } + + // Check for uniform outputs at their default values. + NodeDefPtr shaderNodeDef = shader->getNodeDef(); + if (shaderNodeDef) + { + for (InputPtr shaderInput : shader->getInputs()) + { + OutputPtr output = shaderInput->getConnectedOutput(); + if (output && _bakedConstantMap.count(output)) + { + InputPtr input = shaderNodeDef->getInput(shaderInput->getName()); + if (input) + { + Color4 uniformColor = _bakedConstantMap[output].color; + string uniformColorString = getValueStringFromColor(uniformColor, input->getType()); + string defaultValueString = input->hasValue() ? input->getValue()->getValueString() : EMPTY_STRING; + if (uniformColorString == defaultValueString) + { + _bakedConstantMap[output].isDefault = true; + } + } + } + } + } + + // Remove baked images that have been replaced by constant values. + for (auto& pair : _bakedConstantMap) + { + if (pair.second.isDefault || _optimizeConstants || _averageImages) + { + _bakedImageMap.erase(pair.first); + } + } +} + +template +DocumentPtr TextureBaker::generateNewDocumentFromShader(NodePtr shader, const StringVec& udimSet) +{ + if (!shader) + { + return nullptr; + } + + // Create document. + DocumentPtr bakedTextureDoc = createDocument(); + if (shader->getDocument()->hasColorSpace()) + { + bakedTextureDoc->setColorSpace(shader->getDocument()->getColorSpace()); + } + + // Create node graph and geometry info. + NodeGraphPtr bakedNodeGraph; + if (!_bakedImageMap.empty()) + { + _bakedGraphName = bakedTextureDoc->createValidChildName(_bakedGraphName); + bakedNodeGraph = bakedTextureDoc->addNodeGraph(_bakedGraphName); + bakedNodeGraph->setColorSpace(_colorSpace); + } + _bakedGeomInfoName = bakedTextureDoc->createValidChildName(_bakedGeomInfoName); + GeomInfoPtr bakedGeom = !udimSet.empty() ? bakedTextureDoc->addGeomInfo(_bakedGeomInfoName) : nullptr; + if (bakedGeom) + { + bakedGeom->setGeomPropValue(UDIM_SET_PROPERTY, udimSet, "stringarray"); + } + + // Create a shader node. + NodePtr bakedShader = bakedTextureDoc->addNode(shader->getCategory(), shader->getName() + BAKED_POSTFIX, shader->getType()); + + // Optionally create a material node, connecting it to the new shader node. + if (_material) + { + string materialName = (_texTemplateOverrides.count("$MATERIAL"))? _texTemplateOverrides["$MATERIAL"] : _material->getName(); + NodePtr bakedMaterial = bakedTextureDoc->addNode(_material->getCategory(), materialName + BAKED_POSTFIX, _material->getType()); + for (auto sourceMaterialInput : _material->getInputs()) + { + const string& sourceMaterialInputName = sourceMaterialInput->getName(); + NodePtr upstreamShader = sourceMaterialInput->getConnectedNode(); + if (upstreamShader && (upstreamShader->getNamePath() == shader->getNamePath())) + { + InputPtr bakedMaterialInput = bakedMaterial->getInput(sourceMaterialInputName); + if (!bakedMaterialInput) + { + bakedMaterialInput = bakedMaterial->addInput(sourceMaterialInputName, sourceMaterialInput->getType()); + } + bakedMaterialInput->setNodeName(bakedShader->getName()); + } + } + } + + // Create and connect inputs on the new shader node. + for (ValueElementPtr valueElem : shader->getChildrenOfType()) + { + // Get the source input and its connected output. + InputPtr sourceInput = valueElem->asA(); + if (!sourceInput) + { + continue; + } + + OutputPtr output = sourceInput->getConnectedOutput(); + + // Skip uniform outputs at their default values. + if (output && _bakedConstantMap.count(output) && _bakedConstantMap[output].isDefault) + { + continue; + } + + // Find or create the baked input. + const string& sourceName = sourceInput->getName(); + const string& sourceType = sourceInput->getType(); + InputPtr bakedInput = bakedShader->getInput(sourceName); + if (!bakedInput) + { + bakedInput = bakedShader->addInput(sourceName, sourceType); + } + + // Assign image or constant data to the baked input. + if (output) + { + // Store a constant value for uniform outputs. + if (_optimizeConstants && _bakedConstantMap.count(output)) + { + Color4 uniformColor = _bakedConstantMap[output].color; + string uniformColorString = getValueStringFromColor(uniformColor, bakedInput->getType()); + bakedInput->setValueString(uniformColorString); + if (bakedInput->getType() == "color3" || bakedInput->getType() == "color4") + { + bakedInput->setColorSpace(_colorSpace); + } + continue; + } + + if (!_bakedImageMap.empty()) + { + // Add the image node. + NodePtr bakedImage = bakedNodeGraph->addNode("image", sourceName + BAKED_POSTFIX, sourceType); + InputPtr input = bakedImage->addInput("file", "filename"); + StringMap filenameTemplateMap = initializeFileTemplateMap(bakedInput, shader, udimSet.empty() ? EMPTY_STRING : UDIM_TOKEN); + input->setValueString(generateTextureFilename(filenameTemplateMap)); + + // Reconstruct any world-space nodes that were excluded from the baking process. + auto worldSpacePair = _worldSpaceNodes.find(sourceInput->getName()); + if (worldSpacePair != _worldSpaceNodes.end()) + { + NodePtr origWorldSpaceNode = worldSpacePair->second; + if (origWorldSpaceNode) + { + NodePtr newWorldSpaceNode = bakedNodeGraph->addNode(origWorldSpaceNode->getCategory(), sourceName + BAKED_POSTFIX + "_map", sourceType); + newWorldSpaceNode->copyContentFrom(origWorldSpaceNode); + InputPtr mapInput = newWorldSpaceNode->getInput("in"); + if (mapInput) + { + mapInput->setNodeName(bakedImage->getName()); + } + bakedImage = newWorldSpaceNode; + } + } + + // Add the graph output. + OutputPtr bakedOutput = bakedNodeGraph->addOutput(sourceName + "_output", sourceType); + bakedOutput->setConnectedNode(bakedImage); + bakedInput->setConnectedOutput(bakedOutput); + } + } + else + { + bakedInput->copyContentFrom(sourceInput); + } + } + + // Generate uniform images and write to disk. + ImagePtr uniformImage = createUniformImage(4, 4, 4, Renderer::_baseType, Color4()); + for (const auto& pair : _bakedImageMap) + { + for (const BakedImage& baked : pair.second) + { + if (baked.isUniform) + { + uniformImage->setUniformColor(baked.uniformColor); + writeBakedImage(baked, uniformImage); + } + } + } + + // Clear cached information after each material bake + _bakedImageMap.clear(); + _bakedConstantMap.clear(); + _worldSpaceNodes.clear(); + _bakedInputMap.clear(); + _material = nullptr; + + // Return the baked document on success. + return bakedTextureDoc; +} + +template +DocumentPtr TextureBaker::bakeMaterialToDoc(DocumentPtr doc, const FileSearchPath& searchPath, const string& materialPath, + const StringVec& udimSet, string& documentName) +{ + if (_outputStream) + { + *_outputStream << "Processing material: " << materialPath << std::endl; + } + + // Set up generator context for material + GenContext genContext(_generator); + genContext.getOptions().targetColorSpaceOverride = LIN_REC709; + genContext.getOptions().fileTextureVerticalFlip = true; + genContext.getOptions().targetDistanceUnit = _distanceUnit; + + DefaultColorManagementSystemPtr cms = DefaultColorManagementSystem::create(genContext.getShaderGenerator().getTarget()); + cms->loadLibrary(doc); + genContext.registerSourceCodeSearchPath(searchPath); + genContext.getShaderGenerator().setColorManagementSystem(cms); + + // Compute the material tag set. + StringVec materialTags = udimSet; + if (materialTags.empty()) + { + materialTags.push_back(EMPTY_STRING); + } + + ElementPtr elem = doc->getDescendant(materialPath); + if (!elem || !elem->isA()) + { + return nullptr; + } + NodePtr materialNode = elem->asA(); + + vector shaderNodes = getShaderNodes(materialNode); + NodePtr shaderNode = shaderNodes.empty() ? nullptr : shaderNodes[0]; + if (!shaderNode) + { + return nullptr; + } + + StringResolverPtr resolver = StringResolver::create(); + + // Iterate over material tags. + for (const string& tag : materialTags) + { + // Always clear any cached implementations before generation. + genContext.clearNodeImplementations(); + + ShaderPtr hwShader = createShader("Shader", genContext, shaderNode); + if (!hwShader) + { + continue; + } + Renderer::_imageHandler->setSearchPath(searchPath); + resolver->setUdimString(tag); + Renderer::_imageHandler->setFilenameResolver(resolver); + bakeShaderInputs(materialNode, shaderNode, genContext, tag); + + // Optimize baked textures. + optimizeBakedTextures(shaderNode); + } + + // Link the baked material and textures in a MaterialX document. + documentName = shaderNode->getName(); + return generateNewDocumentFromShader(shaderNode, udimSet); +} + +template +void TextureBaker::bakeAllMaterials(DocumentPtr doc, const FileSearchPath& searchPath, const FilePath& outputFilename) +{ + if (_outputImagePath.isEmpty()) + { + _outputImagePath = outputFilename.getParentPath(); + if (!_outputImagePath.exists()) + { + _outputImagePath.createDirectory(); + } + } + + std::vector renderableMaterials = findRenderableElements(doc); + + // Compute the UDIM set. + ValuePtr udimSetValue = doc->getGeomPropValue(UDIM_SET_PROPERTY); + StringVec udimSet; + if (udimSetValue && udimSetValue->isA()) + { + udimSet = udimSetValue->asA(); + } + + // Bake all materials in documents to memory. + BakedDocumentVec bakedDocuments; + for (size_t i = 0; i < renderableMaterials.size(); i++) + { + if (_outputStream && i > 0) + { + *_outputStream << std::endl; + } + + const TypedElementPtr& element = renderableMaterials[i]; + string documentName; + DocumentPtr bakedMaterialDoc = bakeMaterialToDoc(doc, searchPath, element->getNamePath(), udimSet, documentName); + if (bakedMaterialDoc) + { + bakedDocuments.push_back(make_pair(documentName, bakedMaterialDoc)); + } + } + + // Write documents in memory to disk. + size_t bakeCount = bakedDocuments.size(); + for (size_t i = 0; i < bakeCount; i++) + { + if (bakedDocuments[i].second) + { + FilePath writeFilename = outputFilename; + + // Add additional filename decorations if there are multiple documents. + if (bakedDocuments.size() > 1) + { + const string extension = writeFilename.getExtension(); + writeFilename.removeExtension(); + string filenameSeparator = writeFilename.isDirectory()? EMPTY_STRING : "_"; + writeFilename = FilePath(writeFilename.asString() + filenameSeparator + bakedDocuments[i].first + "." + extension); + } + + writeToXmlFile(bakedDocuments[i].second, writeFilename); + if (_outputStream) + { + *_outputStream << "Wrote baked document: " << writeFilename.asString() << std::endl; + } + } + } +} + +template +void TextureBaker::setupUnitSystem(DocumentPtr unitDefinitions) +{ + UnitTypeDefPtr distanceTypeDef = unitDefinitions ? unitDefinitions->getUnitTypeDef("distance") : nullptr; + UnitTypeDefPtr angleTypeDef = unitDefinitions ? unitDefinitions->getUnitTypeDef("angle") : nullptr; + if (!distanceTypeDef && !angleTypeDef) + { + return; + } + + UnitSystemPtr unitSystem = UnitSystem::create(_generator->getTarget()); + if (!unitSystem) + { + return; + } + _generator->setUnitSystem(unitSystem); + UnitConverterRegistryPtr registry = UnitConverterRegistry::create(); + registry->addUnitConverter(distanceTypeDef, LinearUnitConverter::create(distanceTypeDef)); + registry->addUnitConverter(angleTypeDef, LinearUnitConverter::create(angleTypeDef)); + _generator->getUnitSystem()->loadLibrary(unitDefinitions); + _generator->getUnitSystem()->setUnitConverterRegistry(registry); +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderGlsl/CMakeLists.txt b/source/MaterialXRenderGlsl/CMakeLists.txt index 92139162da..e13af14db1 100644 --- a/source/MaterialXRenderGlsl/CMakeLists.txt +++ b/source/MaterialXRenderGlsl/CMakeLists.txt @@ -58,7 +58,8 @@ elseif(APPLE) ${COMMON_LIBRARIES} ${OPENGL_LIBRARIES} "-framework Foundation" - "-framework Cocoa") + "-framework Cocoa" + "-framework Metal") elseif(UNIX) target_link_libraries( MaterialXRenderGlsl diff --git a/source/MaterialXRenderGlsl/GlslMaterial.cpp b/source/MaterialXRenderGlsl/GlslMaterial.cpp index 8bc7f4ebf4..f14b3634db 100644 --- a/source/MaterialXRenderGlsl/GlslMaterial.cpp +++ b/source/MaterialXRenderGlsl/GlslMaterial.cpp @@ -91,45 +91,6 @@ bool GlslMaterial::generateShader(ShaderPtr hwShader) return true; } -bool GlslMaterial::generateEnvironmentShader(GenContext& context, - const FilePath& filename, - DocumentPtr stdLib, - const FilePath& imagePath) -{ - // Read in the environment nodegraph. - DocumentPtr doc = createDocument(); - doc->importLibrary(stdLib); - DocumentPtr envDoc = createDocument(); - readFromXmlFile(envDoc, filename); - doc->importLibrary(envDoc); - - NodeGraphPtr envGraph = doc->getNodeGraph("environmentDraw"); - if (!envGraph) - { - return false; - } - NodePtr image = envGraph->getNode("envImage"); - if (!image) - { - return false; - } - image->setInputValue("file", imagePath.asString(), FILENAME_TYPE_STRING); - OutputPtr output = envGraph->getOutput("out"); - if (!output) - { - return false; - } - - // Create the shader. - std::string shaderName = "__ENV_SHADER__"; - _hwShader = createShader(shaderName, context, output); - if (!_hwShader) - { - return false; - } - return generateShader(_hwShader); -} - bool GlslMaterial::bindShader() const { if (!_glProgram) diff --git a/source/MaterialXRenderGlsl/GlslMaterial.h b/source/MaterialXRenderGlsl/GlslMaterial.h index 57b58bfaaa..28ed87450b 100644 --- a/source/MaterialXRenderGlsl/GlslMaterial.h +++ b/source/MaterialXRenderGlsl/GlslMaterial.h @@ -9,6 +9,7 @@ /// @file /// GLSL material helper classes +#include #include #include @@ -18,24 +19,12 @@ MATERIALX_NAMESPACE_BEGIN using GlslMaterialPtr = std::shared_ptr; -/// @class ShadowState -/// Helper class representing shadow rendering state -class MX_RENDERGLSL_API ShadowState -{ - public: - ImagePtr shadowMap; - Matrix44 shadowMatrix; - ImagePtr ambientOcclusionMap; - float ambientOcclusionGain = 0.0f; -}; - /// @class GlslMaterial /// Helper class for GLSL generation and rendering of a material -class MX_RENDERGLSL_API GlslMaterial +class MX_RENDERGLSL_API GlslMaterial : public ShaderMaterial { public: - GlslMaterial() : - _hasTransparency(false) + GlslMaterial() : ShaderMaterial() { } ~GlslMaterial() { } @@ -45,83 +34,23 @@ class MX_RENDERGLSL_API GlslMaterial return std::make_shared(); } - /// Set the renderable element associated with this material - void setDocument(DocumentPtr doc) - { - _doc = doc; - } - - /// Return the document associated with this material - DocumentPtr getDocument() const - { - return _doc; - } - - /// Set the renderable element associated with this material - void setElement(TypedElementPtr val) - { - _elem = val; - } - - /// Return the renderable element associated with this material - TypedElementPtr getElement() const - { - return _elem; - } - - /// Set the material node associated with this material - void setMaterialNode(NodePtr node) - { - _materialNode = node; - } - - /// Return the material node associated with this material - NodePtr getMaterialNode() const - { - return _materialNode; - } - - /// Set udim identifier - void setUdim(const std::string& val) - { - _udim = val; - } - - /// Get any associated udim identifier - const std::string& getUdim() - { - return _udim; - } - /// Load shader source from file. bool loadSource(const FilePath& vertexShaderFile, const FilePath& pixelShaderFile, - bool hasTransparency); + bool hasTransparency) override; /// Generate a shader from our currently stored element and /// the given generator context. - bool generateShader(GenContext& context); + bool generateShader(GenContext& context) override; /// Generate a shader from the given hardware shader. - bool generateShader(ShaderPtr hwShader); - - /// Generate an environment background shader - bool generateEnvironmentShader(GenContext& context, - const FilePath& filename, - DocumentPtr stdLib, - const FilePath& imagePath); - + bool generateShader(ShaderPtr hwShader) override; + /// Copy shader from one material to this one - void copyShader(GlslMaterialPtr material) + void copyShader(MaterialPtr material) override { - _hwShader = material->_hwShader; - _glProgram = material->_glProgram; - } - - /// Return the underlying hardware shader. - ShaderPtr getShader() const - { - return _hwShader; + _hwShader = std::static_pointer_cast(material)->_hwShader; + _glProgram = std::static_pointer_cast(material)->_glProgram; } /// Return the underlying GLSL program. @@ -130,72 +59,63 @@ class MX_RENDERGLSL_API GlslMaterial return _glProgram; } - /// Return true if this material has transparency. - bool hasTransparency() const - { - return _hasTransparency; - } - /// Bind shader - bool bindShader() const; + bool bindShader() const override; /// Bind viewing information for this material. - void bindViewInformation(CameraPtr camera); + void bindViewInformation(CameraPtr camera) override; /// Bind all images for this material. - void bindImages(ImageHandlerPtr imageHandler, const FileSearchPath& searchPath, bool enableMipmaps = true); + void bindImages(ImageHandlerPtr imageHandler, + const FileSearchPath& searchPath, + bool enableMipmaps = true) override; /// Unbbind all images for this material. - void unbindImages(ImageHandlerPtr imageHandler); + void unbindImages(ImageHandlerPtr imageHandler) override; /// Bind a single image. - ImagePtr bindImage(const FilePath& filePath, const std::string& uniformName, ImageHandlerPtr imageHandler, - const ImageSamplingProperties& samplingProperties); + ImagePtr bindImage(const FilePath& filePath, + const std::string& uniformName, + ImageHandlerPtr imageHandler, + const ImageSamplingProperties& samplingProperties) override; /// Bind lights to shader. - void bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr imageHandler, const ShadowState& shadowState); + void bindLighting(LightHandlerPtr lightHandler, + ImageHandlerPtr imageHandler, + const ShadowState& shadowState) override; /// Bind units. - void bindUnits(UnitConverterRegistryPtr& registry, const GenContext& context); + void bindUnits(UnitConverterRegistryPtr& registry, + const GenContext& context) override; /// Bind the given mesh to this material. - void bindMesh(MeshPtr mesh); + void bindMesh(MeshPtr mesh) override; /// Bind a mesh partition to this material. - bool bindPartition(MeshPartitionPtr part) const; + bool bindPartition(MeshPartitionPtr part) const override; /// Draw the given mesh partition. - void drawPartition(MeshPartitionPtr part) const; + void drawPartition(MeshPartitionPtr part) const override; /// Unbind all geometry from this material. - void unbindGeometry(); + void unbindGeometry() override; /// Return the block of public uniforms for this material. - VariableBlock* getPublicUniforms() const; + VariableBlock* getPublicUniforms() const override; /// Find a public uniform from its MaterialX path. - ShaderPort* findUniform(const std::string& path) const; + ShaderPort* findUniform(const std::string& path) const override; /// Modify the value of the uniform with the given path. - void modifyUniform(const std::string& path, ConstValuePtr value, std::string valueString = EMPTY_STRING); + void modifyUniform(const std::string& path, + ConstValuePtr value, + std::string valueString = EMPTY_STRING) override; protected: - void clearShader(); + void clearShader() override; protected: - ShaderPtr _hwShader; GlslProgramPtr _glProgram; - - MeshPtr _boundMesh; - - DocumentPtr _doc; - TypedElementPtr _elem; - NodePtr _materialNode; - - std::string _udim; - bool _hasTransparency; - - ImageVec _boundImages; }; MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderGlsl/GlslRenderer.h b/source/MaterialXRenderGlsl/GlslRenderer.h index 8532440e28..4eb77cd0a5 100644 --- a/source/MaterialXRenderGlsl/GlslRenderer.h +++ b/source/MaterialXRenderGlsl/GlslRenderer.h @@ -13,6 +13,7 @@ #include #include +#include #include @@ -44,6 +45,12 @@ class MX_RENDERGLSL_API GlslRenderer : public ShaderRenderer /// Create a GLSL renderer instance static GlslRendererPtr create(unsigned int width = 512, unsigned int height = 512, Image::BaseType baseType = Image::BaseType::UINT8); + /// Create a texture handler for OpenGL textures + ImageHandlerPtr createImageHandler(ImageLoaderPtr imageLoader) + { + return GLTextureHandler::create(imageLoader); + } + /// Destructor virtual ~GlslRenderer() { } diff --git a/source/MaterialXRenderGlsl/TextureBaker.cpp b/source/MaterialXRenderGlsl/TextureBaker.cpp index f08377ecb6..6db0982ed0 100644 --- a/source/MaterialXRenderGlsl/TextureBaker.cpp +++ b/source/MaterialXRenderGlsl/TextureBaker.cpp @@ -14,613 +14,8 @@ #include MATERIALX_NAMESPACE_BEGIN - -namespace { - -const string SRGB_TEXTURE = "srgb_texture"; -const string LIN_REC709 = "lin_rec709"; -const string BAKED_POSTFIX = "_baked"; -const string SHADER_PREFIX = "SR_"; -const string DEFAULT_UDIM_PREFIX = "_"; - -string getValueStringFromColor(const Color4& color, const string& type) -{ - if (type == "color4" || type == "vector4") - { - return toValueString(color); - } - if (type == "color3" || type == "vector3") - { - return toValueString(Vector3(color[0], color[1], color[2])); - } - if (type == "vector2") - { - return toValueString(Vector2(color[0], color[1])); - } - if (type == "float") - { - return toValueString(color[0]); - } - return EMPTY_STRING; -} - -} // anonymous namespace - -TextureBaker::TextureBaker(unsigned int width, unsigned int height, Image::BaseType baseType) : - GlslRenderer(width, height, baseType), - _distanceUnit("meter"), - _averageImages(false), - _optimizeConstants(true), - _bakedGraphName("NG_baked"), - _bakedGeomInfoName("GI_baked"), - _textureFilenameTemplate("$MATERIAL_$SHADINGMODEL_$INPUT$UDIMPREFIX$UDIM.$EXTENSION"), - _outputStream(&std::cout), - _hashImageNames(false), - _textureSpaceMin(0.0f), - _textureSpaceMax(1.0f), - _generator(GlslShaderGenerator::create()), - _permittedOverrides({ "$ASSET", "$MATERIAL", "$UDIMPREFIX" }) -{ - if (baseType == Image::BaseType::UINT8) - { -#if MATERIALX_BUILD_OIIO - _extension = ImageLoader::TIFF_EXTENSION; -#else - _extension = ImageLoader::PNG_EXTENSION; -#endif - _colorSpace = SRGB_TEXTURE; - } - else - { -#if MATERIALX_BUILD_OIIO - _extension = ImageLoader::EXR_EXTENSION; -#else - _extension = ImageLoader::HDR_EXTENSION; -#endif - _colorSpace = LIN_REC709; - } - - // Initialize our base renderer. - GlslRenderer::initialize(); - - // Initialize our image handler. - _imageHandler = GLTextureHandler::create(StbImageLoader::create()); -#if MATERIALX_BUILD_OIIO - _imageHandler->addLoader(OiioImageLoader::create()); -#endif - - // Create our dedicated frame capture image. - _frameCaptureImage = Image::create(width, height, 4, baseType); - _frameCaptureImage->createResourceBuffer(); -} - -size_t TextureBaker::findVarInTemplate(const string& filename, const string& var, size_t start) -{ - size_t i = filename.find(var, start); - if (var == "$UDIM" && i != string::npos) - { - size_t udimPrefix = filename.find("$UDIMPREFIX", start); - if (i == udimPrefix) - { - i = filename.find(var, i + 1); - } - } - return i; -} - -FilePath TextureBaker::generateTextureFilename(const StringMap& filenameTemplateMap) -{ - string bakedImageName = _textureFilenameTemplate; - - for (auto& pair : filenameTemplateMap) - { - string replacement = (_texTemplateOverrides.count(pair.first)) ? - _texTemplateOverrides[pair.first] : pair.second; - replacement = (filenameTemplateMap.at("$UDIM").empty() && pair.first == "$UDIMPREFIX") ? - EMPTY_STRING : replacement; - - for (size_t i = 0; (i = findVarInTemplate(bakedImageName, pair.first, i)) != string::npos; i++) - { - bakedImageName.replace(i, pair.first.length(), replacement); - } - } - - if (_hashImageNames) - { - std::stringstream hashStream; - hashStream << std::hash{}(bakedImageName); - hashStream << "." + getExtension(); - bakedImageName = hashStream.str(); - } - return _outputImagePath / bakedImageName; -} - -StringMap TextureBaker::initializeFileTemplateMap(InputPtr input, NodePtr shader, const string& udim) +TextureBakerGlsl::TextureBakerGlsl(unsigned int width, unsigned int height, Image::BaseType baseType) : + TextureBaker(width, height, baseType, true) { - FilePath assetPath = FilePath(shader->getActiveSourceUri()); - assetPath.removeExtension(); - StringMap filenameTemplateMap; - filenameTemplateMap["$ASSET"] = assetPath.getBaseName(); - filenameTemplateMap["$INPUT"] = _bakedInputMap[input->getName()]; - filenameTemplateMap["$EXTENSION"] = _extension; - filenameTemplateMap["$MATERIAL"] = _material->getName(); - filenameTemplateMap["$SHADINGMODEL"] = shader->getCategory(); - filenameTemplateMap["$UDIM"] = udim; - filenameTemplateMap["$UDIMPREFIX"] = DEFAULT_UDIM_PREFIX; - return filenameTemplateMap; -} - -bool TextureBaker::writeBakedImage(const BakedImage& baked, ImagePtr image) -{ - if (!_imageHandler->saveImage(baked.filename, image, true)) - { - if (_outputStream) - { - *_outputStream << "Failed to write baked image: " << baked.filename.asString() << std::endl; - } - return false; - } - - if (_outputStream) - { - *_outputStream << "Wrote baked image: " << baked.filename.asString() << std::endl; - } - - return true; -} - -void TextureBaker::bakeShaderInputs(NodePtr material, NodePtr shader, GenContext& context, const string& udim) -{ - _material = material; - - if (!shader) - { - return; - } - - std::unordered_map bakedOutputMap; - for (InputPtr input : shader->getInputs()) - { - OutputPtr output = input->getConnectedOutput(); - if (output && !bakedOutputMap.count(output)) - { - bakedOutputMap[output] = input; - _bakedInputMap[input->getName()] = input->getName(); - - // When possible, nodes with world-space outputs are applied outside of the baking process. - NodePtr worldSpaceNode = connectsToWorldSpaceNode(output); - if (worldSpaceNode) - { - output->setConnectedNode(worldSpaceNode->getConnectedNode("in")); - _worldSpaceNodes[input->getName()] = worldSpaceNode; - } - StringMap filenameTemplateMap = initializeFileTemplateMap(input, shader, udim); - bakeGraphOutput(output, context, filenameTemplateMap); - } - else if (bakedOutputMap.count(output)) - { - // When the input shares the same output as a previously baked input, we use the already baked input. - _bakedInputMap[input->getName()] = bakedOutputMap[output]->getName(); - } - } - - // Release all images used to generate this set of shader inputs. - _imageHandler->clearImageCache(); -} - -void TextureBaker::bakeGraphOutput(OutputPtr output, GenContext& context, const StringMap& filenameTemplateMap) -{ - if (!output) - { - return; - } - - ShaderPtr shader = _generator->generate("BakingShader", output, context); - createProgram(shader); - - bool encodeSrgb = _colorSpace == SRGB_TEXTURE && - (output->getType() == "color3" || output->getType() == "color4"); - getFramebuffer()->setEncodeSrgb(encodeSrgb); - - // Render and capture the requested image. - renderTextureSpace(getTextureSpaceMin(), getTextureSpaceMax()); - string texturefilepath = generateTextureFilename(filenameTemplateMap); - captureImage(_frameCaptureImage); - - // Construct a baked image record. - BakedImage baked; - baked.filename = texturefilepath; - if (_averageImages) - { - baked.uniformColor = _frameCaptureImage->getAverageColor(); - baked.isUniform = true; - } - else if (_frameCaptureImage->isUniformColor(&baked.uniformColor)) - { - baked.isUniform = true; - } - _bakedImageMap[output].push_back(baked); - - // TODO: Write images to memory rather than to disk. - // Write non-uniform images to disk. - if (!baked.isUniform) - { - writeBakedImage(baked, _frameCaptureImage); - } -} - -void TextureBaker::optimizeBakedTextures(NodePtr shader) -{ - if (!shader) - { - return; - } - - // Check for fully uniform outputs. - for (auto& pair : _bakedImageMap) - { - bool outputIsUniform = true; - for (BakedImage& baked : pair.second) - { - if (!baked.isUniform || baked.uniformColor != pair.second[0].uniformColor) - { - outputIsUniform = false; - continue; - } - } - if (outputIsUniform) - { - BakedConstant bakedConstant; - bakedConstant.color = pair.second[0].uniformColor; - _bakedConstantMap[pair.first] = bakedConstant; - } - } - - // Check for uniform outputs at their default values. - NodeDefPtr shaderNodeDef = shader->getNodeDef(); - if (shaderNodeDef) - { - for (InputPtr shaderInput : shader->getInputs()) - { - OutputPtr output = shaderInput->getConnectedOutput(); - if (output && _bakedConstantMap.count(output)) - { - InputPtr input = shaderNodeDef->getInput(shaderInput->getName()); - if (input) - { - Color4 uniformColor = _bakedConstantMap[output].color; - string uniformColorString = getValueStringFromColor(uniformColor, input->getType()); - string defaultValueString = input->hasValue() ? input->getValue()->getValueString() : EMPTY_STRING; - if (uniformColorString == defaultValueString) - { - _bakedConstantMap[output].isDefault = true; - } - } - } - } - } - - // Remove baked images that have been replaced by constant values. - for (auto& pair : _bakedConstantMap) - { - if (pair.second.isDefault || _optimizeConstants || _averageImages) - { - _bakedImageMap.erase(pair.first); - } - } } - -DocumentPtr TextureBaker::generateNewDocumentFromShader(NodePtr shader, const StringVec& udimSet) -{ - if (!shader) - { - return nullptr; - } - - // Create document. - DocumentPtr bakedTextureDoc = createDocument(); - if (shader->getDocument()->hasColorSpace()) - { - bakedTextureDoc->setColorSpace(shader->getDocument()->getColorSpace()); - } - - // Create node graph and geometry info. - NodeGraphPtr bakedNodeGraph; - if (!_bakedImageMap.empty()) - { - _bakedGraphName = bakedTextureDoc->createValidChildName(_bakedGraphName); - bakedNodeGraph = bakedTextureDoc->addNodeGraph(_bakedGraphName); - bakedNodeGraph->setColorSpace(_colorSpace); - } - _bakedGeomInfoName = bakedTextureDoc->createValidChildName(_bakedGeomInfoName); - GeomInfoPtr bakedGeom = !udimSet.empty() ? bakedTextureDoc->addGeomInfo(_bakedGeomInfoName) : nullptr; - if (bakedGeom) - { - bakedGeom->setGeomPropValue(UDIM_SET_PROPERTY, udimSet, "stringarray"); - } - - // Create a shader node. - NodePtr bakedShader = bakedTextureDoc->addNode(shader->getCategory(), shader->getName() + BAKED_POSTFIX, shader->getType()); - - // Optionally create a material node, connecting it to the new shader node. - if (_material) - { - string materialName = (_texTemplateOverrides.count("$MATERIAL"))? _texTemplateOverrides["$MATERIAL"] : _material->getName(); - NodePtr bakedMaterial = bakedTextureDoc->addNode(_material->getCategory(), materialName + BAKED_POSTFIX, _material->getType()); - for (auto sourceMaterialInput : _material->getInputs()) - { - const string& sourceMaterialInputName = sourceMaterialInput->getName(); - NodePtr upstreamShader = sourceMaterialInput->getConnectedNode(); - if (upstreamShader && (upstreamShader->getNamePath() == shader->getNamePath())) - { - InputPtr bakedMaterialInput = bakedMaterial->getInput(sourceMaterialInputName); - if (!bakedMaterialInput) - { - bakedMaterialInput = bakedMaterial->addInput(sourceMaterialInputName, sourceMaterialInput->getType()); - } - bakedMaterialInput->setNodeName(bakedShader->getName()); - } - } - } - - // Create and connect inputs on the new shader node. - for (ValueElementPtr valueElem : shader->getChildrenOfType()) - { - // Get the source input and its connected output. - InputPtr sourceInput = valueElem->asA(); - if (!sourceInput) - { - continue; - } - - OutputPtr output = sourceInput->getConnectedOutput(); - - // Skip uniform outputs at their default values. - if (output && _bakedConstantMap.count(output) && _bakedConstantMap[output].isDefault) - { - continue; - } - - // Find or create the baked input. - const string& sourceName = sourceInput->getName(); - const string& sourceType = sourceInput->getType(); - InputPtr bakedInput = bakedShader->getInput(sourceName); - if (!bakedInput) - { - bakedInput = bakedShader->addInput(sourceName, sourceType); - } - - // Assign image or constant data to the baked input. - if (output) - { - // Store a constant value for uniform outputs. - if (_optimizeConstants && _bakedConstantMap.count(output)) - { - Color4 uniformColor = _bakedConstantMap[output].color; - string uniformColorString = getValueStringFromColor(uniformColor, bakedInput->getType()); - bakedInput->setValueString(uniformColorString); - if (bakedInput->getType() == "color3" || bakedInput->getType() == "color4") - { - bakedInput->setColorSpace(_colorSpace); - } - continue; - } - - if (!_bakedImageMap.empty()) - { - // Add the image node. - NodePtr bakedImage = bakedNodeGraph->addNode("image", sourceName + BAKED_POSTFIX, sourceType); - InputPtr input = bakedImage->addInput("file", "filename"); - StringMap filenameTemplateMap = initializeFileTemplateMap(bakedInput, shader, udimSet.empty() ? EMPTY_STRING : UDIM_TOKEN); - input->setValueString(generateTextureFilename(filenameTemplateMap)); - - // Reconstruct any world-space nodes that were excluded from the baking process. - auto worldSpacePair = _worldSpaceNodes.find(sourceInput->getName()); - if (worldSpacePair != _worldSpaceNodes.end()) - { - NodePtr origWorldSpaceNode = worldSpacePair->second; - if (origWorldSpaceNode) - { - NodePtr newWorldSpaceNode = bakedNodeGraph->addNode(origWorldSpaceNode->getCategory(), sourceName + BAKED_POSTFIX + "_map", sourceType); - newWorldSpaceNode->copyContentFrom(origWorldSpaceNode); - InputPtr mapInput = newWorldSpaceNode->getInput("in"); - if (mapInput) - { - mapInput->setNodeName(bakedImage->getName()); - } - bakedImage = newWorldSpaceNode; - } - } - - // Add the graph output. - OutputPtr bakedOutput = bakedNodeGraph->addOutput(sourceName + "_output", sourceType); - bakedOutput->setConnectedNode(bakedImage); - bakedInput->setConnectedOutput(bakedOutput); - } - } - else - { - bakedInput->copyContentFrom(sourceInput); - } - } - - // Generate uniform images and write to disk. - ImagePtr uniformImage = createUniformImage(4, 4, 4, _baseType, Color4()); - for (const auto& pair : _bakedImageMap) - { - for (const BakedImage& baked : pair.second) - { - if (baked.isUniform) - { - uniformImage->setUniformColor(baked.uniformColor); - writeBakedImage(baked, uniformImage); - } - } - } - - // Clear cached information after each material bake - _bakedImageMap.clear(); - _bakedConstantMap.clear(); - _worldSpaceNodes.clear(); - _bakedInputMap.clear(); - _material = nullptr; - - // Return the baked document on success. - return bakedTextureDoc; -} - -DocumentPtr TextureBaker::bakeMaterialToDoc(DocumentPtr doc, const FileSearchPath& searchPath, const string& materialPath, - const StringVec& udimSet, string& documentName) -{ - if (_outputStream) - { - *_outputStream << "Processing material: " << materialPath << std::endl; - } - - // Set up generator context for material - GenContext genContext(_generator); - genContext.getOptions().targetColorSpaceOverride = LIN_REC709; - genContext.getOptions().fileTextureVerticalFlip = true; - genContext.getOptions().targetDistanceUnit = _distanceUnit; - - DefaultColorManagementSystemPtr cms = DefaultColorManagementSystem::create(genContext.getShaderGenerator().getTarget()); - cms->loadLibrary(doc); - genContext.registerSourceCodeSearchPath(searchPath); - genContext.getShaderGenerator().setColorManagementSystem(cms); - - // Compute the material tag set. - StringVec materialTags = udimSet; - if (materialTags.empty()) - { - materialTags.push_back(EMPTY_STRING); - } - - ElementPtr elem = doc->getDescendant(materialPath); - if (!elem || !elem->isA()) - { - return nullptr; - } - NodePtr materialNode = elem->asA(); - - vector shaderNodes = getShaderNodes(materialNode); - NodePtr shaderNode = shaderNodes.empty() ? nullptr : shaderNodes[0]; - if (!shaderNode) - { - return nullptr; - } - - StringResolverPtr resolver = StringResolver::create(); - - // Iterate over material tags. - for (const string& tag : materialTags) - { - // Always clear any cached implementations before generation. - genContext.clearNodeImplementations(); - - ShaderPtr hwShader = createShader("Shader", genContext, shaderNode); - if (!hwShader) - { - continue; - } - _imageHandler->setSearchPath(searchPath); - resolver->setUdimString(tag); - _imageHandler->setFilenameResolver(resolver); - bakeShaderInputs(materialNode, shaderNode, genContext, tag); - - // Optimize baked textures. - optimizeBakedTextures(shaderNode); - } - - // Link the baked material and textures in a MaterialX document. - documentName = shaderNode->getName(); - return generateNewDocumentFromShader(shaderNode, udimSet); -} - -void TextureBaker::bakeAllMaterials(DocumentPtr doc, const FileSearchPath& searchPath, const FilePath& outputFilename) -{ - if (_outputImagePath.isEmpty()) - { - _outputImagePath = outputFilename.getParentPath(); - if (!_outputImagePath.exists()) - { - _outputImagePath.createDirectory(); - } - } - - std::vector renderableMaterials = findRenderableElements(doc); - - // Compute the UDIM set. - ValuePtr udimSetValue = doc->getGeomPropValue(UDIM_SET_PROPERTY); - StringVec udimSet; - if (udimSetValue && udimSetValue->isA()) - { - udimSet = udimSetValue->asA(); - } - - // Bake all materials in documents to memory. - BakedDocumentVec bakedDocuments; - for (size_t i = 0; i < renderableMaterials.size(); i++) - { - if (_outputStream && i > 0) - { - *_outputStream << std::endl; - } - - const TypedElementPtr& element = renderableMaterials[i]; - string documentName; - DocumentPtr bakedMaterialDoc = bakeMaterialToDoc(doc, searchPath, element->getNamePath(), udimSet, documentName); - if (bakedMaterialDoc) - { - bakedDocuments.push_back(make_pair(documentName, bakedMaterialDoc)); - } - } - - // Write documents in memory to disk. - size_t bakeCount = bakedDocuments.size(); - for (size_t i = 0; i < bakeCount; i++) - { - if (bakedDocuments[i].second) - { - FilePath writeFilename = outputFilename; - - // Add additional filename decorations if there are multiple documents. - if (bakedDocuments.size() > 1) - { - const string extension = writeFilename.getExtension(); - writeFilename.removeExtension(); - string filenameSeparator = writeFilename.isDirectory()? EMPTY_STRING : "_"; - writeFilename = FilePath(writeFilename.asString() + filenameSeparator + bakedDocuments[i].first + "." + extension); - } - - writeToXmlFile(bakedDocuments[i].second, writeFilename); - if (_outputStream) - { - *_outputStream << "Wrote baked document: " << writeFilename.asString() << std::endl; - } - } - } -} - -void TextureBaker::setupUnitSystem(DocumentPtr unitDefinitions) -{ - UnitTypeDefPtr distanceTypeDef = unitDefinitions ? unitDefinitions->getUnitTypeDef("distance") : nullptr; - UnitTypeDefPtr angleTypeDef = unitDefinitions ? unitDefinitions->getUnitTypeDef("angle") : nullptr; - if (!distanceTypeDef && !angleTypeDef) - { - return; - } - - UnitSystemPtr unitSystem = UnitSystem::create(_generator->getTarget()); - if (!unitSystem) - { - return; - } - _generator->setUnitSystem(unitSystem); - UnitConverterRegistryPtr registry = UnitConverterRegistry::create(); - registry->addUnitConverter(distanceTypeDef, LinearUnitConverter::create(distanceTypeDef)); - registry->addUnitConverter(angleTypeDef, LinearUnitConverter::create(angleTypeDef)); - _generator->getUnitSystem()->loadLibrary(unitDefinitions); - _generator->getUnitSystem()->setUnitConverterRegistry(registry); -} - MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderGlsl/TextureBaker.h b/source/MaterialXRenderGlsl/TextureBaker.h index fede01b120..b92c568979 100644 --- a/source/MaterialXRenderGlsl/TextureBaker.h +++ b/source/MaterialXRenderGlsl/TextureBaker.h @@ -3,8 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 // -#ifndef MATERIALX_TEXTUREBAKER -#define MATERIALX_TEXTUREBAKER +#ifndef MATERIALX_TEXTUREBAKER_GLSL +#define MATERIALX_TEXTUREBAKER_GLSL /// @file /// Texture baking functionality @@ -12,18 +12,18 @@ #include #include +#include #include #include #include - #include MATERIALX_NAMESPACE_BEGIN /// A shared pointer to a TextureBaker -using TextureBakerPtr = shared_ptr; +using TextureBakerPtr = shared_ptr; /// A vector of baked documents with their associated names. using BakedDocumentVec = std::vector>; @@ -32,269 +32,15 @@ using BakedDocumentVec = std::vector>; /// A helper class for baking procedural material content to textures. /// TODO: Add support for graphs containing geometric nodes such as position /// and normal. -class MX_RENDERGLSL_API TextureBaker : public GlslRenderer +class MX_RENDERGLSL_API TextureBakerGlsl : public TextureBaker { - public: +public: static TextureBakerPtr create(unsigned int width = 1024, unsigned int height = 1024, Image::BaseType baseType = Image::BaseType::UINT8) { - return TextureBakerPtr(new TextureBaker(width, height, baseType)); - } - - /// Set the file extension for baked textures. - void setExtension(const string& extension) - { - _extension = extension; - } - - /// Return the file extension for baked textures. - const string& getExtension() const - { - return _extension; - } - - /// Set the color space in which color textures are encoded. - /// - /// By default, this color space is srgb_texture, and color inputs are - /// automatically transformed to this space by the baker. If another color - /// space is set, then the input graph is responsible for transforming - /// colors to this space. - void setColorSpace(const string& colorSpace) - { - _colorSpace = colorSpace; - } - - /// Return the color space in which color textures are encoded. - const string& getColorSpace() const - { - return _colorSpace; - } - - /// Set the distance unit to which textures are baked. Defaults to meters. - void setDistanceUnit(const string& unitSpace) - { - _distanceUnit = unitSpace; - } - - /// Return the distance unit to which textures are baked. - const string& getDistanceUnit() const - { - return _distanceUnit; - } - - /// Set whether images should be averaged to generate constants. Defaults to false. - void setAverageImages(bool enable) - { - _averageImages = enable; - } - - /// Return whether images should be averaged to generate constants. - bool getAverageImages() const - { - return _averageImages; - } - - /// Set whether uniform textures should be stored as constants. Defaults to true. - void setOptimizeConstants(bool enable) - { - _optimizeConstants = enable; - } - - /// Return whether uniform textures should be stored as constants. - bool getOptimizeConstants() const - { - return _optimizeConstants; - } - - /// Set the output location for baked texture images. Defaults to the root folder - /// of the destination material. - void setOutputImagePath(const FilePath& outputImagePath) - { - _outputImagePath = outputImagePath; - } - - /// Get the current output location for baked texture images. - const FilePath& getOutputImagePath() - { - return _outputImagePath; - } - - /// Set the name of the baked graph element. - void setBakedGraphName(const string& name) - { - _bakedGraphName= name; - } - - /// Return the name of the baked graph element. - const string& getBakedGraphName() const - { - return _bakedGraphName; - } - - /// Set the name of the baked geometry info element. - void setBakedGeomInfoName(const string& name) - { - _bakedGeomInfoName = name; - } - - /// Return the name of the baked geometry info element. - const string& getBakedGeomInfoName() const - { - return _bakedGeomInfoName; - } - - /// Get the texture filename template. - const string& getTextureFilenameTemplate() const - { - return _textureFilenameTemplate; - } - - /// Set the texture filename template. - void setTextureFilenameTemplate(const string& filenameTemplate) - { - _textureFilenameTemplate = (filenameTemplate.find("$EXTENSION") == string::npos) ? - filenameTemplate + ".$EXTENSION" : filenameTemplate; - } - - /// Set texFilenameOverrides if template variable exists. - void setFilenameTemplateVarOverride(const string& key, const string& value) - { - if (_permittedOverrides.count(key)) - { - _texTemplateOverrides[key] = value; - } - } - - /// Set the output stream for reporting progress and warnings. Defaults to std::cout. - void setOutputStream(std::ostream* outputStream) - { - _outputStream = outputStream; - } - - /// Return the output stream for reporting progress and warnings. - std::ostream* getOutputStream() const - { - return _outputStream; - } - - /// Set whether to create a short name for baked images by hashing the baked image filenames - /// This is useful for file systems which may have a maximum limit on filename size. - /// By default names are not hashed. - void setHashImageNames(bool enable) - { - _hashImageNames = enable; - } - - /// Return whether automatic baked texture resolution is set. - bool getHashImageNames() const - { - return _hashImageNames; - } - - /// Set the minimum texcoords used in texture baking. Defaults to 0, 0. - void setTextureSpaceMin(const Vector2& min) - { - _textureSpaceMin = min; - } - - /// Return the minimum texcoords used in texture baking. - Vector2 getTextureSpaceMin() const - { - return _textureSpaceMin; - } - - /// Set the maximum texcoords used in texture baking. Defaults to 1, 1. - void setTextureSpaceMax(const Vector2& max) - { - _textureSpaceMax = max; - } - - /// Return the maximum texcoords used in texture baking. - Vector2 getTextureSpaceMax() const - { - return _textureSpaceMax; + return TextureBakerPtr(new TextureBakerGlsl(width, height, baseType)); } - - /// Set up the unit definitions to be used in baking. - void setupUnitSystem(DocumentPtr unitDefinitions); - - /// Bake textures for all graph inputs of the given shader. - void bakeShaderInputs(NodePtr material, NodePtr shader, GenContext& context, const string& udim = EMPTY_STRING); - - /// Bake a texture for the given graph output. - void bakeGraphOutput(OutputPtr output, GenContext& context, const StringMap& filenameTemplateMap); - - /// Optimize baked textures before writing. - void optimizeBakedTextures(NodePtr shader); - - /// Bake material to document in memory and write baked textures to disk. - DocumentPtr bakeMaterialToDoc(DocumentPtr doc, const FileSearchPath& searchPath, const string& materialPath, - const StringVec& udimSet, std::string& documentName); - - /// Bake materials in the given document and write them to disk. If multiple documents are written, - /// then the given output filename will be used as a template. - void bakeAllMaterials(DocumentPtr doc, const FileSearchPath& searchPath, const FilePath& outputFileName); - - protected: - class BakedImage - { - public: - FilePath filename; - Color4 uniformColor; - bool isUniform = false; - }; - class BakedConstant - { - public: - Color4 color; - bool isDefault = false; - }; - using BakedImageVec = vector; - using BakedImageMap = std::unordered_map; - using BakedConstantMap = std::unordered_map; - - protected: - TextureBaker(unsigned int width, unsigned int height, Image::BaseType baseType); - - // Populate file template variable naming map - StringMap initializeFileTemplateMap(InputPtr input, NodePtr shader, const string& udim = EMPTY_STRING); - - // Find first occurence of variable in filename from start index onwards - size_t findVarInTemplate(const string& filename, const string& var, size_t start = 0); - - // Generate a texture filename for the given graph output. - FilePath generateTextureFilename(const StringMap& fileTemplateMap); - - // Create document that links shader outputs to a material. - DocumentPtr generateNewDocumentFromShader(NodePtr shader, const StringVec& udimSet); - - // Write a baked image to disk, returning true if the write was successful. - bool writeBakedImage(const BakedImage& baked, ImagePtr image); - - protected: - string _extension; - string _colorSpace; - string _distanceUnit; - bool _averageImages; - bool _optimizeConstants; - FilePath _outputImagePath; - string _bakedGraphName; - string _bakedGeomInfoName; - string _textureFilenameTemplate; - std::ostream* _outputStream; - bool _hashImageNames; - Vector2 _textureSpaceMin; - Vector2 _textureSpaceMax; - - ShaderGeneratorPtr _generator; - ConstNodePtr _material; - ImagePtr _frameCaptureImage; - BakedImageMap _bakedImageMap; - BakedConstantMap _bakedConstantMap; - StringSet _permittedOverrides; - StringMap _texTemplateOverrides; - StringMap _bakedInputMap; - - std::unordered_map _worldSpaceNodes; + + TextureBakerGlsl(unsigned int width, unsigned int height, Image::BaseType baseType); }; MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderHw/CMakeLists.txt b/source/MaterialXRenderHw/CMakeLists.txt index 16716bea05..85ef13c55c 100644 --- a/source/MaterialXRenderHw/CMakeLists.txt +++ b/source/MaterialXRenderHw/CMakeLists.txt @@ -35,7 +35,8 @@ elseif(APPLE) MaterialXRender ${CMAKE_DL_LIBS} "-framework Foundation" - "-framework Cocoa") + "-framework Cocoa" + "-framework Metal") elseif(UNIX) target_link_libraries( MaterialXRenderHw diff --git a/source/MaterialXRenderMsl/CMakeLists.txt b/source/MaterialXRenderMsl/CMakeLists.txt new file mode 100644 index 0000000000..5ad50ea2cc --- /dev/null +++ b/source/MaterialXRenderMsl/CMakeLists.txt @@ -0,0 +1,94 @@ +file(GLOB_RECURSE materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.m*") +file(GLOB_RECURSE materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*") + +assign_source_group("Source Files" ${materialx_source}) +assign_source_group("Header Files" ${materialx_headers}) + +if(POLICY CMP0072) + cmake_policy(SET CMP0072 NEW) +endif() + +if(APPLE) + find_library(COCOA_FRAMEWORK Cocoa) + find_package(OpenGL REQUIRED) + file(GLOB_RECURSE materialx_source_oc "${CMAKE_CURRENT_SOURCE_DIR}/*.m") + message("Objective C files: " ${materialx_source_oc}) + set_source_files_properties(${materialx_source_oc} PROPERTIES + COMPILE_FLAGS "-x objective-c++") + set(materialx_source ${materialx_source} ${materialx_source_oc}) + add_compile_options(-DGL_SILENCE_DEPRECATION) +elseif(UNIX) + find_package(X11 REQUIRED) + # Note - can't just require the Xt component because FindX11 in cmake 3.1 + # doesn't support it + if(NOT X11_Xt_FOUND) + message(FATAL_ERROR "Error in building MaterialXRenderMsl: Xt was not found") + endif() + + find_package(OpenGL REQUIRED) + include_directories(${X11_INCLUDE_DIR}) +endif() + +# Disable OpenGL deprecation warnings on Clang. +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wno-deprecated-declarations) +endif() + +add_library(MaterialXRenderMsl ${materialx_source} ${materialx_headers}) + +add_definitions(-DMATERIALX_RENDERMSL_EXPORTS) + +set(COMMON_LIBRARIES + MaterialXRenderHw + MaterialXGenMsl + ${CMAKE_DL_LIBS}) + +if(MSVC) + target_link_libraries( + MaterialXRenderMsl + ${COMMON_LIBRARIES} + Opengl32) +elseif(APPLE) + target_link_libraries( + MaterialXRenderMsl + ${COMMON_LIBRARIES} + ${OPENGL_LIBRARIES} + "-framework Foundation" + "-framework Cocoa" + "-framework Metal") +elseif(UNIX) + target_link_libraries( + MaterialXRenderMsl + ${COMMON_LIBRARIES} + ${OPENGL_LIBRARIES} + ${X11_LIBRARIES} + ${X11_Xt_LIB}) +endif() + +set_target_properties( + MaterialXRenderMsl PROPERTIES + OUTPUT_NAME MaterialXRenderMsl${MATERIALX_LIBNAME_SUFFIX} + COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}" + LINK_FLAGS "${EXTERNAL_LINK_FLAGS}" + VERSION "${MATERIALX_LIBRARY_VERSION}" + SOVERSION "${MATERIALX_MAJOR_VERSION}") + +target_include_directories(MaterialXRenderMsl + PUBLIC + $ + $ + PRIVATE + ${EXTERNAL_INCLUDE_DIRS}) + +install(TARGETS MaterialXRenderMsl + 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}/MaterialXRenderMsl/ MESSAGE_NEVER + FILES_MATCHING PATTERN "*.h*") + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/MaterialXRenderMsl.pdb" + DESTINATION "${MATERIALX_INSTALL_LIB_PATH}/" OPTIONAL) diff --git a/source/MaterialXRenderMsl/Export.h b/source/MaterialXRenderMsl/Export.h new file mode 100644 index 0000000000..8295c6312d --- /dev/null +++ b/source/MaterialXRenderMsl/Export.h @@ -0,0 +1,22 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_RENDERMSL_EXPORT_H +#define MATERIALX_RENDERMSL_EXPORT_H + +#include + +/// @file +/// Macros for declaring imported and exported symbols. + +#if defined(MATERIALX_RENDERMSL_EXPORTS) + #define MX_RENDERMSL_API MATERIALX_SYMBOL_EXPORT + #define MX_RENDERMSL_EXTERN_TEMPLATE(...) MATERIALX_EXPORT_EXTERN_TEMPLATE(__VA_ARGS__) +#else + #define MX_RENDERMSL_API MATERIALX_SYMBOL_IMPORT + #define MX_RENDERMSL_EXTERN_TEMPLATE(...) MATERIALX_IMPORT_EXTERN_TEMPLATE(__VA_ARGS__) +#endif + +#endif diff --git a/source/MaterialXRenderMsl/MetalFramebuffer.h b/source/MaterialXRenderMsl/MetalFramebuffer.h new file mode 100644 index 0000000000..f7a0078b43 --- /dev/null +++ b/source/MaterialXRenderMsl/MetalFramebuffer.h @@ -0,0 +1,110 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_METALFRAMEBUFFER_H +#define MATERIALX_METALFRAMEBUFFER_H + +/// @file +/// Metal framebuffer handling + +#include + +#include + +#import + +MATERIALX_NAMESPACE_BEGIN + +class MetalFramebuffer; + +/// Shared pointer to a MetalFramebuffer +using MetalFramebufferPtr = std::shared_ptr; + +/// @class MetalFramebuffer +/// Wrapper for an Metal framebuffer +class MX_RENDERMSL_API MetalFramebuffer +{ + public: + /// Create a new framebuffer + static MetalFramebufferPtr create(id device, + unsigned int width, unsigned int height, + unsigned int channelCount, + Image::BaseType baseType, + id colorTexture = nil, + bool encodeSrgb = false, + MTLPixelFormat pixelFormat = MTLPixelFormatInvalid); + + /// Destructor + virtual ~MetalFramebuffer(); + + /// Resize the framebuffer + void resize(unsigned int width, unsigned int height, bool forceRecreate = false, + MTLPixelFormat pixelFormat = MTLPixelFormatInvalid, + id extColorTexture = nil); + + /// Set the encode sRGB flag, which controls whether values written + /// to the framebuffer are encoded to the sRGB color space. + void setEncodeSrgb(bool encode) + { + if(encode != _encodeSrgb) + { + _encodeSrgb = encode; + resize(_width, _height, true); + } + } + + /// Return the encode sRGB flag. + bool getEncodeSrgb() + { + return _encodeSrgb; + } + + /// Bind the framebuffer for rendering. + void bind(MTLRenderPassDescriptor* renderpassDesc); + + /// Unbind the frame buffer after rendering. + void unbind(); + + /// Return our color texture handle. + id getColorTexture() const + { + return _colorTexture; + } + + /// Return our depth texture handle. + id getDepthTexture() const + { + return _depthTexture; + } + + /// Return the color data of this framebuffer as an image. + /// If an input image is provided, it will be used to store the color data; + /// otherwise a new image of the required format will be created. + ImagePtr getColorImage(id cmdQueue = nil, ImagePtr image = nullptr); + + protected: + MetalFramebuffer(id device, + unsigned int width, unsigned int height, + unsigned int channelCount, + Image::BaseType baseType, + id colorTexture = nil, + bool encodeSrgb = false, + MTLPixelFormat pixelFormat = MTLPixelFormatInvalid); + + protected: + unsigned int _width; + unsigned int _height; + unsigned int _channelCount; + Image::BaseType _baseType; + bool _encodeSrgb; + + id _device = nil; + id _colorTexture = nil; + id _depthTexture = nil; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRenderMsl/MetalFramebuffer.mm b/source/MaterialXRenderMsl/MetalFramebuffer.mm new file mode 100644 index 0000000000..b90c55a6eb --- /dev/null +++ b/source/MaterialXRenderMsl/MetalFramebuffer.mm @@ -0,0 +1,170 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include + +MATERIALX_NAMESPACE_BEGIN + +// +// MetalFramebuffer methods +// + +MetalFramebufferPtr MetalFramebuffer::create(id device, + unsigned int width, unsigned int height, + unsigned channelCount, + Image::BaseType baseType, + id colorTexture, + bool encodeSrgb, + MTLPixelFormat pixelFormat) +{ + return MetalFramebufferPtr(new MetalFramebuffer(device, + width, height, + channelCount, + baseType, + colorTexture, + encodeSrgb, + pixelFormat)); +} + +MetalFramebuffer::MetalFramebuffer(id device, + unsigned int width, unsigned int height, + unsigned int channelCount, + Image::BaseType baseType, + id colorTexture, + bool encodeSrgb, + MTLPixelFormat pixelFormat) : + _width(0), + _height(0), + _channelCount(channelCount), + _baseType(baseType), + _encodeSrgb(encodeSrgb), + _device(device), + _colorTexture(colorTexture), + _depthTexture(0) +{ + StringVec errors; + const string errorType("Metal target creation failure."); + + resize(width, height, true, pixelFormat, colorTexture); +} + +MetalFramebuffer::~MetalFramebuffer() +{ + [_colorTexture release]; + [_depthTexture release]; +} + +void MetalFramebuffer::resize(unsigned int width, unsigned int height, bool forceRecreate, + MTLPixelFormat pixelFormat, + id extColorTexture) +{ + if (width * height <= 0) + { + return; + } + if (width != _width || _height != height || forceRecreate) + { + // Convert texture format to Metal + MTLDataType dataType; + if(pixelFormat == MTLPixelFormatInvalid) + MetalTextureHandler::mapTextureFormatToMetal(_baseType, _channelCount, _encodeSrgb, dataType, pixelFormat); + + MTLTextureDescriptor* texDescriptor = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:pixelFormat + width:width + height:height + mipmapped:NO]; + [texDescriptor setStorageMode:MTLStorageModePrivate]; + [texDescriptor setUsage:MTLTextureUsageRenderTarget|MTLTextureUsageShaderRead]; + + if(extColorTexture == nil) + { + _colorTexture = [_device newTextureWithDescriptor:texDescriptor]; + } + else + { + _colorTexture = extColorTexture; + } + + texDescriptor.pixelFormat = MTLPixelFormatDepth32Float; + [texDescriptor setUsage:MTLTextureUsageRenderTarget]; + _depthTexture = [_device newTextureWithDescriptor:texDescriptor]; + + _width = width; + _height = height; + } +} + +void MetalFramebuffer::bind(MTLRenderPassDescriptor* renderpassDesc) +{ + [renderpassDesc.colorAttachments[0] setTexture:getColorTexture()]; + [renderpassDesc.colorAttachments[0] setLoadAction:MTLLoadActionClear]; + [renderpassDesc.colorAttachments[0] setStoreAction:MTLStoreActionStore]; + + [renderpassDesc.depthAttachment setTexture:getDepthTexture()]; + [renderpassDesc.depthAttachment setClearDepth:1.0]; + [renderpassDesc.depthAttachment setLoadAction:MTLLoadActionClear]; + [renderpassDesc.depthAttachment setStoreAction:MTLStoreActionStore]; + [renderpassDesc setStencilAttachment:nil]; + + [renderpassDesc setRenderTargetWidth:_width]; + [renderpassDesc setRenderTargetHeight:_height]; +} + +void MetalFramebuffer::unbind() +{ +} + +ImagePtr MetalFramebuffer::getColorImage(id cmdQueue, ImagePtr image) +{ + if (!image) + { + image = Image::create(_width, _height, _channelCount, _baseType); + image->createResourceBuffer(); + } + + if(cmdQueue == nil) + { + return image; + } + + size_t bytesPerRow = _width*_channelCount*MetalTextureHandler::getTextureBaseTypeSize(_baseType); + size_t bytesPerImage = _height * bytesPerRow; + + id buffer = [_device newBufferWithLength:bytesPerImage options:MTLResourceStorageModeShared]; + + id cmdBuffer = [cmdQueue commandBuffer]; + + id blitCmdEncoder = [cmdBuffer blitCommandEncoder]; + [blitCmdEncoder copyFromTexture:_colorTexture + sourceSlice:0 + sourceLevel:0 + sourceOrigin:MTLOriginMake(0, 0, 0) + sourceSize:MTLSizeMake(_width, _height, 1) + toBuffer:buffer destinationOffset:0 + destinationBytesPerRow:bytesPerRow + destinationBytesPerImage:bytesPerImage + options:MTLBlitOptionNone]; + + + [blitCmdEncoder endEncoding]; + + [cmdBuffer commit]; + [cmdBuffer waitUntilCompleted]; + + std::vector imageData(bytesPerImage); + memcpy(imageData.data(), [buffer contents], bytesPerImage); + + memcpy(image->getResourceBuffer(), imageData.data(), bytesPerImage); + [buffer release]; + + return image; +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderMsl/MetalState.h b/source/MaterialXRenderMsl/MetalState.h new file mode 100644 index 0000000000..15eacc360a --- /dev/null +++ b/source/MaterialXRenderMsl/MetalState.h @@ -0,0 +1,77 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALXVIEW_METALSTATE_H +#define MATERIALXVIEW_METALSTATE_H + +#include +#include +#include +#include + +#import + +#include + +MATERIALX_NAMESPACE_BEGIN +class MetalFramebuffer; +// Shared pointer to a MetalFramebuffer +using MetalFramebufferPtr = std::shared_ptr; +MATERIALX_NAMESPACE_END + +struct MetalState +{ + static MetalState* getSingleton() + { + if(!singleton) + { + singleton = std::unique_ptr(new MetalState()); + } + return singleton.get(); + } + + MetalState(); + + void initialize(id mtlDevice, id mtlCmdQueue); + void initLinearToSRGBKernel(); + void triggerProgrammaticCapture(); + void stopProgrammaticCapture(); + void beginCommandBuffer(); + void beginEncoder(MTLRenderPassDescriptor* renderpassDesc); + void endEncoder(); + void endCommandBuffer(); + + void waitForComplition(); + + MaterialX::MetalFramebufferPtr currentFramebuffer(); + + static std::unique_ptr singleton; + + id device = nil; + id cmdQueue = nil; + id cmdBuffer = nil; + id linearToSRGB_pso = nil; + id renderCmdEncoder = nil; + std::stack framebufferStack; + + bool supportsTiledPipeline; + + id opaqueDepthStencilState = nil; + id transparentDepthStencilState = nil; + id envMapDepthStencilState = nil; + + std::condition_variable inFlightCV; + std::mutex inFlightMutex; + std::atomic inFlightCommandBuffers; +}; + +#define MTL(a) (MetalState::getSingleton()->a) +#define MTL_DEPTHSTENCIL_STATE(a) (MetalState::getSingleton()->a##DepthStencilState) +#define MTL_TRIGGER_CAPTURE MetalState::getSingleton()->triggerProgrammaticCapture() +#define MTL_STOP_CAPTURE MetalState::getSingleton()->stopProgrammaticCapture() +#define MTL_PUSH_FRAMEBUFFER(a) MetalState::getSingleton()->framebufferStack.push(a) +#define MTL_POP_FRAMEBUFFER(a) MetalState::getSingleton()->framebufferStack.pop() + +#endif // MATERIALXVIEW_METALSTATE_H diff --git a/source/MaterialXRenderMsl/MetalState.mm b/source/MaterialXRenderMsl/MetalState.mm new file mode 100644 index 0000000000..941b00c526 --- /dev/null +++ b/source/MaterialXRenderMsl/MetalState.mm @@ -0,0 +1,237 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include "MetalState.h" +#import + +#include + +std::unique_ptr MetalState::singleton = nullptr; + +MetalState::MetalState() +{ +} + +void MetalState::initialize(id mtlDevice, id mtlCmdQueue) +{ + device = mtlDevice; + cmdQueue = mtlCmdQueue; + +#ifdef MAC_OS_VERSION_11_0 + if (@available(macOS 11.0, ios 14.0, *)) + { + supportsTiledPipeline = [device supportsFamily:MTLGPUFamilyApple4]; + } +#else + supportsTiledPipeline = false; +#endif + + MTLDepthStencilDescriptor* depthStencilDesc = [MTLDepthStencilDescriptor new]; + depthStencilDesc.depthWriteEnabled = true; + depthStencilDesc.depthCompareFunction = MTLCompareFunctionLess; + opaqueDepthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDesc]; + + depthStencilDesc.depthWriteEnabled = false; + depthStencilDesc.depthCompareFunction = MTLCompareFunctionLess; + transparentDepthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDesc]; + + depthStencilDesc.depthWriteEnabled = true; + depthStencilDesc.depthCompareFunction = MTLCompareFunctionAlways; + envMapDepthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDesc]; + + initLinearToSRGBKernel(); +} + +void MetalState::initLinearToSRGBKernel() +{ + NSError* error = nil; + MTLCompileOptions* options = [MTLCompileOptions new]; +#ifdef MAC_OS_VERSION_11_0 + if (@available(macOS 11.0, ios 14.0, *)) + options.languageVersion = MTLLanguageVersion2_3; + else +#endif + options.languageVersion = MTLLanguageVersion2_0; + options.fastMathEnabled = true; + +#ifdef MAC_OS_VERSION_11_0 + bool useTiledPipeline = supportsTiledPipeline; + if(useTiledPipeline) + { + if(@available(macOS 11.0, ios 14.0, *)) + { + NSString* linearToSRGB_kernel = + @"#include \n" + "#include \n" + " \n" + "using namespace metal; \n" + " \n" + "struct RenderTarget { \n" + " half4 colorTarget [[color(0)]]; \n" + "}; \n" + " \n" + " \n" + " \n" + "half4 linearToSRGB(half4 color_linear) \n" + "{ \n" + " half4 color_srgb; \n" + " for(int i = 0; i < 3; ++i) \n" + " color_srgb[i] = (color_linear[i] < 0.0031308) ? \n" + " (12.92 * color_linear[i]) : \n" + " (1.055 * pow(color_linear[i], 1.0h / 2.2h) - 0.055); \n" + " color_srgb[3] = color_linear[3]; \n" + " return color_srgb; \n" + "} \n" + " \n" + "kernel void LinearToSRGB_kernel( \n" + " imageblock imageBlock, \n" + " ushort2 tid [[ thread_position_in_threadgroup ]]) \n" + "{ \n" + " RenderTarget linearValue = imageBlock.read(tid); \n" + " RenderTarget srgbValue; \n" + " srgbValue.colorTarget = linearToSRGB(linearValue.colorTarget); \n" + " imageBlock.write(srgbValue, tid); \n" + "} \n"; + + id library = [device newLibraryWithSource:linearToSRGB_kernel options:options error:&error]; + id function = [library newFunctionWithName:@"LinearToSRGB_kernel"]; + + MTLTileRenderPipelineDescriptor* renderPipelineDescriptor = [MTLTileRenderPipelineDescriptor new]; + [renderPipelineDescriptor setRasterSampleCount:1]; + [[renderPipelineDescriptor colorAttachments][0] setPixelFormat:MTLPixelFormatBGRA8Unorm]; + [renderPipelineDescriptor setTileFunction:function]; + linearToSRGB_pso = [device newRenderPipelineStateWithTileDescriptor:renderPipelineDescriptor options:0 reflection:nil error:&error]; + } + else + { + useTiledPipeline = false; + } + } + + if(!useTiledPipeline) +#endif + { + NSString* linearToSRGB_kernel = + @"#include \n" + "#include \n" + " \n" + "using namespace metal; \n" + " \n" + "struct VSOutput \n" + "{ \n" + " float4 position [[position]]; \n" + "}; \n" + " \n" + "vertex VSOutput VertexMain(uint vertexId [[ vertex_id ]]) \n" + "{ \n" + " VSOutput vsOut; \n" + " \n" + " switch(vertexId) \n" + " { \n" + " case 0: vsOut.position = float4(-1, -1, 0.5, 1); break; \n" + " case 1: vsOut.position = float4(-1, 3, 0.5, 1); break; \n" + " case 2: vsOut.position = float4( 3, -1, 0.5, 1); break; \n" + " }; \n" + " \n" + " return vsOut; \n" + "} \n" + " \n" + "half4 linearToSRGB(half4 color_linear) \n" + "{ \n" + " half4 color_srgb; \n" + " for(int i = 0; i < 3; ++i) \n" + " color_srgb[i] = (color_linear[i] < 0.0031308) ? \n" + " (12.92 * color_linear[i]) : \n" + " (1.055 * pow(color_linear[i], 1.0h / 2.2h) - 0.055);\n" + " color_srgb[3] = color_linear[3]; \n" + " return color_srgb; \n" + "} \n" + " \n" + "fragment half4 FragmentMain( \n" + " texture2d inputTex [[ texture(0) ]], \n" + " float4 fragCoord [[ position ]] \n" + ") \n" + "{ \n" + " constexpr sampler ss( \n" + " coord::pixel, \n" + " address::clamp_to_border, \n" + " filter::linear); \n" + " return linearToSRGB(inputTex.sample(ss, fragCoord.xy)); \n" + "} \n"; + + id library = [device newLibraryWithSource:linearToSRGB_kernel options:options error:&error]; + + id vertexfunction = [library newFunctionWithName:@"VertexMain"]; + id Fragmentfunction = [library newFunctionWithName:@"FragmentMain"]; + + MTLRenderPipelineDescriptor* renderPipelineDesc = [MTLRenderPipelineDescriptor new]; + [renderPipelineDesc setVertexFunction:vertexfunction]; + [renderPipelineDesc setFragmentFunction:Fragmentfunction]; + [[renderPipelineDesc colorAttachments][0] setPixelFormat:MTLPixelFormatBGRA8Unorm]; + [renderPipelineDesc setDepthAttachmentPixelFormat:MTLPixelFormatDepth32Float]; + linearToSRGB_pso = [device newRenderPipelineStateWithDescriptor:renderPipelineDesc error:&error]; + } +} + +void MetalState::triggerProgrammaticCapture() +{ + MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; + MTLCaptureDescriptor* captureDescriptor = [MTLCaptureDescriptor new]; + + [captureDescriptor setCaptureObject:device]; + + NSError* error = nil; + if(![captureManager startCaptureWithDescriptor:captureDescriptor error:&error]) + { + NSLog(@"Failed to start capture, error %@", error); + } +} + +void MetalState::stopProgrammaticCapture() +{ + MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; + [captureManager stopCapture]; +} + +void MetalState::beginCommandBuffer() +{ + cmdBuffer = [cmdQueue commandBuffer]; + inFlightCommandBuffers++; +} + +void MetalState::beginEncoder(MTLRenderPassDescriptor* renderpassDesc) +{ + renderCmdEncoder = [cmdBuffer + renderCommandEncoderWithDescriptor:renderpassDesc]; +} + +void MetalState::endEncoder() +{ + [renderCmdEncoder endEncoding]; +} + +void MetalState::endCommandBuffer() +{ + endEncoder(); + [cmdBuffer addCompletedHandler:^(id _Nonnull) { + inFlightCommandBuffers--; + inFlightCV.notify_one(); + }]; + [cmdBuffer commit]; + [cmdBuffer waitUntilCompleted]; +} + +void MetalState::waitForComplition() +{ + std::unique_lock lock(inFlightMutex); + while (inFlightCommandBuffers != 0){ + inFlightCV.wait(lock, [this]{ return inFlightCommandBuffers.load() == 0; }); + } +} + +MaterialX::MetalFramebufferPtr MetalState::currentFramebuffer() +{ + return framebufferStack.top(); +} diff --git a/source/MaterialXRenderMsl/MetalTextureHandler.h b/source/MaterialXRenderMsl/MetalTextureHandler.h new file mode 100644 index 0000000000..a2937ede2c --- /dev/null +++ b/source/MaterialXRenderMsl/MetalTextureHandler.h @@ -0,0 +1,97 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_GLTEXTUREHANDLER_H +#define MATERIALX_GLTEXTUREHANDLER_H + +/// @file +/// Metal texture handler + +#include + +#include + +#include +#include + +#import + +MATERIALX_NAMESPACE_BEGIN + +/// Shared pointer to an Metal texture handler +using MetalTextureHandlerPtr = std::shared_ptr; + +/// @class MetalTextureHandler +/// An Metal texture handler class +class MX_RENDERMSL_API MetalTextureHandler : public ImageHandler +{ + friend class MslProgram; + public: + static MetalTextureHandlerPtr create(id device, ImageLoaderPtr imageLoader) + { + return MetalTextureHandlerPtr(new MetalTextureHandler(device, imageLoader)); + } + + + /// This method binds image and its corresponding sampling properties. + /// It also creates the underlying resource if needed. + /// Actual binding of texture and sampler to command encoder happens autoamt + bool bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties) override; + +protected: + /// Bind an image. This method will bind the texture to an active texture + /// unit as defined by the corresponding image description. The method + /// will fail if there are not enough available image units to bind to. + bool bindImage(id renderCmdEncoder, + int textureUnit, + ImagePtr image); +public: + id getSamplerState(const ImageSamplingProperties& samplingProperties); + + /// Unbind an image. + bool unbindImage(ImagePtr image) override; + + id getMTLTextureForImage(unsigned int index) const; + id getMTLSamplerStateForImage(unsigned int index); + + /// Create rendering resources for the given image. + bool createRenderResources(ImagePtr image, bool generateMipMaps) override; + + /// Release rendering resources for the given image, or for all cached images + /// if no image pointer is specified. + void releaseRenderResources(ImagePtr image = nullptr) override; + + /// Return the bound texture location for a given resource + int getBoundTextureLocation(unsigned int resourceId); + + /// Utility to map an address mode enumeration to an Metal address mode + static MTLSamplerAddressMode mapAddressModeToMetal(ImageSamplingProperties::AddressMode addressModeEnum); + + /// Utility to map a filter type enumeration to an Metal filter type + static void mapFilterTypeToMetal(ImageSamplingProperties::FilterType filterTypeEnum, bool enableMipmaps, MTLSamplerMinMagFilter& minMagFilter, MTLSamplerMipFilter& mipFilter); + + /// Utility to map generic texture properties to Metal texture formats. + static void mapTextureFormatToMetal(Image::BaseType baseType, unsigned int channelCount, bool srgb, + MTLDataType& dataType, MTLPixelFormat& pixelFormat); + + static size_t getTextureBaseTypeSize(Image::BaseType baseType); + + protected: + // Protected constructor + MetalTextureHandler(id device, ImageLoaderPtr imageLoader); + + protected: + std::vector _boundTextureLocations; + + std::unordered_map> _metalTextureMap; + std::unordered_map> _imageBindingInfo; + std::unordered_map, ImageSamplingKeyHasher> _imageSamplerStateMap; + + id _device = nil; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRenderMsl/MetalTextureHandler.mm b/source/MaterialXRenderMsl/MetalTextureHandler.mm new file mode 100644 index 0000000000..0406821f4e --- /dev/null +++ b/source/MaterialXRenderMsl/MetalTextureHandler.mm @@ -0,0 +1,429 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +MetalTextureHandler::MetalTextureHandler(id device, ImageLoaderPtr imageLoader) : + ImageHandler(imageLoader) +{ + int maxTextureUnits = 31; + _boundTextureLocations.resize(maxTextureUnits, MslProgram::UNDEFINED_METAL_RESOURCE_ID); + _device = device; +} + +bool MetalTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties) +{ + // Create renderer resources if needed. + if (image->getResourceId() == MslProgram::UNDEFINED_METAL_RESOURCE_ID) + { + if (!createRenderResources(image, true)) + { + return false; + } + } + _imageBindingInfo[image->getResourceId()] = std::make_pair(image, samplingProperties); + return true; +} + +id MetalTextureHandler::getSamplerState(const ImageSamplingProperties& samplingProperties) +{ + if(_imageSamplerStateMap.find(samplingProperties) == _imageSamplerStateMap.end()) + { + MTLSamplerDescriptor* samplerDesc = [MTLSamplerDescriptor new]; + [samplerDesc setSAddressMode:mapAddressModeToMetal(samplingProperties.uaddressMode)]; + [samplerDesc setRAddressMode:mapAddressModeToMetal(samplingProperties.uaddressMode)]; + [samplerDesc setTAddressMode:mapAddressModeToMetal(samplingProperties.vaddressMode)]; + [samplerDesc setBorderColor:samplingProperties.defaultColor[0] == 0 ? MTLSamplerBorderColorOpaqueBlack : MTLSamplerBorderColorOpaqueWhite]; + MTLSamplerMinMagFilter minmagFilter; + MTLSamplerMipFilter mipFilter; + mapFilterTypeToMetal(samplingProperties.filterType, samplingProperties.enableMipmaps, minmagFilter, mipFilter); + // Magnification filters are more restrictive than minification + [samplerDesc setMagFilter:MTLSamplerMinMagFilterLinear]; + [samplerDesc setMinFilter:minmagFilter]; + [samplerDesc setMipFilter:mipFilter]; + [samplerDesc setMaxAnisotropy:16]; + + _imageSamplerStateMap[samplingProperties] = [_device newSamplerStateWithDescriptor:samplerDesc]; + } + + return _imageSamplerStateMap[samplingProperties]; +} + +bool MetalTextureHandler::bindImage(id renderCmdEncoder, + int textureUnit, ImagePtr image) +{ + // Create renderer resources if needed. + if (image->getResourceId() == MslProgram::UNDEFINED_METAL_RESOURCE_ID) + { + if (!createRenderResources(image, true)) + { + return false; + } + } + + _boundTextureLocations[textureUnit] = image->getResourceId(); + + [renderCmdEncoder setFragmentTexture:_metalTextureMap[image->getResourceId()] atIndex:textureUnit]; + [renderCmdEncoder setFragmentSamplerState:getSamplerState(_imageBindingInfo[image->getResourceId()].second) atIndex:textureUnit]; + + return true; +} + +id MetalTextureHandler::getMTLTextureForImage(unsigned int index) const +{ + auto imageInfo = _imageBindingInfo.find(index); + if(imageInfo != _imageBindingInfo.end()) + { + if(!imageInfo->second.first) + return nil; + + auto metalTexture = _metalTextureMap.find(imageInfo->second.first->getResourceId()); + if(metalTexture != _metalTextureMap.end()) + return metalTexture->second; + } + + return nil; +} + +id MetalTextureHandler::getMTLSamplerStateForImage(unsigned int index) +{ + auto imageInfo = _imageBindingInfo.find(index); + if(imageInfo != _imageBindingInfo.end()) + { + return getSamplerState(imageInfo->second.second); + } + return nil; +} + +bool MetalTextureHandler::unbindImage(ImagePtr image) +{ + if (image->getResourceId() != MslProgram::UNDEFINED_METAL_RESOURCE_ID) + { + int textureUnit = getBoundTextureLocation(image->getResourceId()); + if (textureUnit >= 0) + { + _boundTextureLocations[textureUnit] = MslProgram::UNDEFINED_METAL_RESOURCE_ID; + return true; + } + } + return false; +} + +bool MetalTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps) +{ + id texture = nil; + + MTLPixelFormat pixelFormat; + MTLDataType dataType; + + if (image->getResourceId() == MslProgram::UNDEFINED_METAL_RESOURCE_ID) + { + static unsigned int resourceId = 0; + ++resourceId; + + mapTextureFormatToMetal(image->getBaseType(), image->getChannelCount(), + false, dataType, pixelFormat); + + MTLTextureDescriptor* texDesc = [MTLTextureDescriptor new]; + [texDesc setTextureType:MTLTextureType2D]; + texDesc.width = image->getWidth(); + texDesc.height = image->getHeight(); + texDesc.mipmapLevelCount = generateMipMaps ? image->getMaxMipCount() : 1; + texDesc.usage = MTLTextureUsageShaderRead; + texDesc.resourceOptions = MTLResourceStorageModePrivate; + texDesc.pixelFormat = pixelFormat; + if(image->getChannelCount() == 1) + { + texDesc.swizzle = MTLTextureSwizzleChannelsMake( + MTLTextureSwizzleRed, + MTLTextureSwizzleRed, + MTLTextureSwizzleRed, + MTLTextureSwizzleRed); + } + else if(image->getChannelCount() == 2) + { + texDesc.swizzle = MTLTextureSwizzleChannelsMake( + MTLTextureSwizzleRed, + MTLTextureSwizzleGreen, + MTLTextureSwizzleRed, + MTLTextureSwizzleGreen); + } + texture = [_device newTextureWithDescriptor:texDesc]; + _metalTextureMap[resourceId] = texture; + image->setResourceId(resourceId); + } + else + { + mapTextureFormatToMetal(image->getBaseType(), image->getChannelCount(), false, + dataType, pixelFormat); + + texture = _metalTextureMap[image->getResourceId()]; + } + + + id cmdQueue = [_device newCommandQueue]; + id cmdBuffer = [cmdQueue commandBuffer]; + + id blitCmdEncoder = [cmdBuffer blitCommandEncoder]; + + NSUInteger channelCount = image->getChannelCount(); + + NSUInteger sourceBytesPerRow = + image->getWidth() * + channelCount * + getTextureBaseTypeSize(image->getBaseType()); + NSUInteger sourceBytesPerImage = + sourceBytesPerRow * + image->getHeight(); + + std::vector rearrangedDataF; + std::vector rearrangedDataC; + void* imageData = image->getResourceBuffer(); + + if ((pixelFormat == MTLPixelFormatRGBA32Float || pixelFormat == MTLPixelFormatRGBA8Unorm) && channelCount == 3) + { + bool isFloat = pixelFormat == MTLPixelFormatRGBA32Float; + + sourceBytesPerRow = sourceBytesPerRow / 3 * 4; + sourceBytesPerImage = sourceBytesPerImage / 3 * 4; + + size_t srcIdx = 0; + + if(isFloat) + { + rearrangedDataF.resize(sourceBytesPerImage / sizeof(float)); + for(size_t dstIdx = 0; dstIdx < rearrangedDataF.size(); ++dstIdx) + { + if((dstIdx & 0x3) == 3) + { + rearrangedDataF[dstIdx] = 1.0f; + continue; + } + + rearrangedDataF[dstIdx] = ((float*)imageData)[srcIdx++]; + } + + imageData = rearrangedDataF.data(); + } + else + { + rearrangedDataC.resize(sourceBytesPerImage); + for(size_t dstIdx = 0; dstIdx < rearrangedDataC.size(); ++dstIdx) + { + if((dstIdx & 0x3) == 3) + { + rearrangedDataC[dstIdx] = 255; + continue; + } + + rearrangedDataC[dstIdx] = ((unsigned char*)imageData)[srcIdx++]; + } + + imageData = rearrangedDataC.data(); + } + + channelCount = 4; + } + + id buffer = nil; + if(imageData) + { + buffer = [_device newBufferWithBytes:imageData + length:sourceBytesPerImage + options:MTLStorageModeShared]; + [blitCmdEncoder copyFromBuffer:buffer sourceOffset:0 + sourceBytesPerRow:sourceBytesPerRow + sourceBytesPerImage:sourceBytesPerImage + sourceSize:MTLSizeMake(image->getWidth(), image->getHeight(), 1) + toTexture:texture + destinationSlice:0 + destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + } + + if(generateMipMaps && image->getMaxMipCount() > 1) + [blitCmdEncoder generateMipmapsForTexture:texture]; + + [blitCmdEncoder endEncoding]; + + [cmdBuffer commit]; + [cmdBuffer waitUntilCompleted]; + + if(buffer) + [buffer release]; + + return true; +} + +void MetalTextureHandler::releaseRenderResources(ImagePtr image) +{ + if(!image) + return; + + if (image->getResourceId() == MslProgram::UNDEFINED_METAL_RESOURCE_ID) + { + return; + } + + unbindImage(image); + unsigned int resourceId = image->getResourceId(); + _metalTextureMap.erase(resourceId); + image->setResourceId(MslProgram::UNDEFINED_METAL_RESOURCE_ID); +} + +int MetalTextureHandler::getBoundTextureLocation(unsigned int resourceId) +{ + for (size_t i = 0; i < _boundTextureLocations.size(); i++) + { + if (_boundTextureLocations[i] == resourceId) + { + return static_cast(i); + } + } + return -1; +} + +MTLSamplerAddressMode MetalTextureHandler::mapAddressModeToMetal(ImageSamplingProperties::AddressMode addressModeEnum) +{ + const vector addressModes + { + // Constant color. Use clamp to border + // with border color to achieve this + MTLSamplerAddressModeClampToBorderColor, + + // Clamp + MTLSamplerAddressModeClampToEdge, + + // Repeat + MTLSamplerAddressModeRepeat, + + // Mirror + MTLSamplerAddressModeMirrorRepeat + }; + + MTLSamplerAddressMode addressMode = MTLSamplerAddressModeRepeat; + if (addressModeEnum != ImageSamplingProperties::AddressMode::UNSPECIFIED) + { + addressMode = addressModes[static_cast(addressModeEnum)]; + } + return addressMode; +} + +void MetalTextureHandler::mapFilterTypeToMetal(ImageSamplingProperties::FilterType filterTypeEnum, bool enableMipmaps, MTLSamplerMinMagFilter& minMagFilter, MTLSamplerMipFilter& mipFilter) +{ + if(enableMipmaps) + { + if(filterTypeEnum == ImageSamplingProperties::FilterType::LINEAR || + filterTypeEnum == ImageSamplingProperties::FilterType::CUBIC || + filterTypeEnum == ImageSamplingProperties::FilterType::UNSPECIFIED) + { + minMagFilter = MTLSamplerMinMagFilterLinear; + mipFilter = MTLSamplerMipFilterLinear; + } + else + { + minMagFilter = MTLSamplerMinMagFilterNearest; + mipFilter = MTLSamplerMipFilterNearest; + } + } + else + { + if(filterTypeEnum == ImageSamplingProperties::FilterType::LINEAR || + filterTypeEnum == ImageSamplingProperties::FilterType::CUBIC || + filterTypeEnum == ImageSamplingProperties::FilterType::UNSPECIFIED) + { + minMagFilter = MTLSamplerMinMagFilterLinear; + mipFilter = MTLSamplerMipFilterNotMipmapped; + } + else + { + minMagFilter = MTLSamplerMinMagFilterNearest; + mipFilter = MTLSamplerMipFilterNotMipmapped; + } + } +} + +void MetalTextureHandler::mapTextureFormatToMetal(Image::BaseType baseType, unsigned int channelCount, bool srgb, + MTLDataType& dataType, MTLPixelFormat& pixelFormat) +{ + if (baseType == Image::BaseType::UINT8) + { + dataType = MTLDataTypeChar; + switch (channelCount) + { + case 4: pixelFormat = srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm; dataType = MTLDataTypeChar4; break; + case 3: pixelFormat = srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm; dataType = MTLDataTypeChar3; break; + case 2: pixelFormat = MTLPixelFormatRG8Unorm; dataType = MTLDataTypeChar2; break; + case 1: pixelFormat = MTLPixelFormatR8Unorm; dataType = MTLDataTypeChar; break; + default: throw Exception("Unsupported channel count in mapTextureFormatToMetal"); + } + } + else if (baseType == Image::BaseType::UINT16) + { + switch (channelCount) + { + case 4: pixelFormat = MTLPixelFormatRGBA16Uint; dataType = MTLDataTypeShort4; break; + case 3: pixelFormat = MTLPixelFormatRGBA16Uint; dataType = MTLDataTypeShort3; break; + case 2: pixelFormat = MTLPixelFormatRG16Uint; dataType = MTLDataTypeShort2; break; + case 1: pixelFormat = MTLPixelFormatR16Uint; dataType = MTLDataTypeShort; break; + default: throw Exception("Unsupported channel count in mapTextureFormatToMetal"); + } + } + else if (baseType == Image::BaseType::HALF) + { + switch (channelCount) + { + case 4: pixelFormat = MTLPixelFormatRGBA16Float; dataType = MTLDataTypeHalf4; break; + case 3: pixelFormat = MTLPixelFormatRGBA16Float; dataType = MTLDataTypeHalf3; break; + case 2: pixelFormat = MTLPixelFormatRG16Float; dataType = MTLDataTypeHalf2; break; + case 1: pixelFormat = MTLPixelFormatR16Float; dataType = MTLDataTypeHalf ; break; + default: throw Exception("Unsupported channel count in mapTextureFormatToMetal"); + } + } + else if (baseType == Image::BaseType::FLOAT) + { + switch (channelCount) + { + case 4: pixelFormat = MTLPixelFormatRGBA32Float; dataType = MTLDataTypeFloat4; break; + case 3: pixelFormat = MTLPixelFormatRGBA32Float; dataType = MTLDataTypeFloat3; break; + case 2: pixelFormat = MTLPixelFormatRG32Float; dataType = MTLDataTypeFloat2; break; + case 1: pixelFormat = MTLPixelFormatR32Float; dataType = MTLDataTypeFloat; break; + default: throw Exception("Unsupported channel count in mapTextureFormatToMetal"); + } + } + else + { + throw Exception("Unsupported base type in mapTextureFormatToMetal"); + } +} + +size_t MetalTextureHandler::getTextureBaseTypeSize(Image::BaseType baseType) +{ + if (baseType == Image::BaseType::UINT8) + { + return 1; + } + else if (baseType == Image::BaseType::UINT16 || baseType == Image::BaseType::HALF) + { + return 2; + } + else if (baseType == Image::BaseType::FLOAT) + { + return 4; + } + else + { + throw Exception("Unsupported base type in mapTextureFormatToMetal"); + } +} + + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderMsl/MslMaterial.h b/source/MaterialXRenderMsl/MslMaterial.h new file mode 100644 index 0000000000..45ce1b5e61 --- /dev/null +++ b/source/MaterialXRenderMsl/MslMaterial.h @@ -0,0 +1,129 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_MSLMATERIAL_H +#define MATERIALX_MSLMATERIAL_H + +/// @file +/// GLSL material helper classes + +#include +#include + +#include +#include + +MATERIALX_NAMESPACE_BEGIN + +using GeometryHandlerPtr = std::shared_ptr; +using MslProgramPtr = std::shared_ptr; +using MslMaterialPtr = std::shared_ptr; + +/// @class MslMaterial +/// Helper class for MSL generation and rendering of a material +class MX_RENDERMSL_API MslMaterial : public ShaderMaterial +{ + public: + MslMaterial() : ShaderMaterial() + { + } + ~MslMaterial() { } + + static MslMaterialPtr create() + { + return std::make_shared(); + } + + /// Load shader source from file. + bool loadSource(const FilePath& vertexShaderFile, + const FilePath& pixelShaderFile, + bool hasTransparency) override; + + /// Generate a shader from our currently stored element and + /// the given generator context. + bool generateShader(GenContext& context) override; + + /// Generate a shader from the given hardware shader. + bool generateShader(ShaderPtr hwShader) override; + + /// Copy shader from one material to this one + void copyShader(MaterialPtr material) override + { + _hwShader = std::static_pointer_cast(material)->_hwShader; + _glProgram = std::static_pointer_cast(material)->_glProgram; + } + + /// Return the underlying MSL program. + MslProgramPtr getProgram() const + { + return _glProgram; + } + + /// Bind shader + bool bindShader() const override; + + /// Bind viewing information for this material. + void bindViewInformation(CameraPtr camera) override; + + /// Bind all images for this material. + void bindImages(ImageHandlerPtr imageHandler, + const FileSearchPath& searchPath, + bool enableMipmaps = true) override; + + /// Unbbind all images for this material. + void unbindImages(ImageHandlerPtr imageHandler) override; + + /// Bind a single image. + ImagePtr bindImage(const FilePath& filePath, + const std::string& uniformName, + ImageHandlerPtr imageHandler, + const ImageSamplingProperties& samplingProperties) override; + + /// Bind lights to shader. + void bindLighting(LightHandlerPtr lightHandler, + ImageHandlerPtr imageHandler, + const ShadowState& shadowState) override; + + /// Bind units. + void bindUnits(UnitConverterRegistryPtr& registry, + const GenContext& context) override; + + /// Bind the given mesh to this material. + void bindMesh(MeshPtr mesh) override; + + /// Bind a mesh partition to this material. + bool bindPartition(MeshPartitionPtr part) const override; + + /// Draw the given mesh partition. + void drawPartition(MeshPartitionPtr part) const override; + + /// Unbind all geometry from this material. + void unbindGeometry() override; + + /// Return the block of public uniforms for this material. + VariableBlock* getPublicUniforms() const override; + + /// Find a public uniform from its MaterialX path. + ShaderPort* findUniform(const std::string& path) const override; + + /// Modify the value of the uniform with the given path. + void modifyUniform(const std::string& path, + ConstValuePtr value, + std::string valueString = EMPTY_STRING) override; + + void prepareUsedResources(CameraPtr cam, + GeometryHandlerPtr geometryHandler, + ImageHandlerPtr imageHandler, + LightHandlerPtr lightHandler); + protected: + void clearShader() override; + + protected: + MslProgramPtr _glProgram; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRenderMsl/MslMaterial.mm b/source/MaterialXRenderMsl/MslMaterial.mm new file mode 100644 index 0000000000..25c1caf6dd --- /dev/null +++ b/source/MaterialXRenderMsl/MslMaterial.mm @@ -0,0 +1,406 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include +#include +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +const std::string DISTANCE_UNIT_TARGET_NAME = "u_distanceUnitTarget"; + +// +// Material methods +// + +bool MslMaterial::loadSource(const FilePath& vertexShaderFile, const FilePath& pixelShaderFile, bool hasTransparency) +{ + _hasTransparency = hasTransparency; + + std::string vertexShader = readFile(vertexShaderFile); + if (vertexShader.empty()) + { + return false; + } + + std::string pixelShader = readFile(pixelShaderFile); + if (pixelShader.empty()) + { + return false; + } + + // TODO: + // Here we set new source code on the _glProgram without rebuilding + // the _hwShader instance. So the _hwShader is not in sync with the + // _glProgram after this operation. + _glProgram = MslProgram::create(); + _glProgram->addStage(Stage::VERTEX, vertexShader); + _glProgram->addStage(Stage::PIXEL, pixelShader); + _glProgram->build(MTL(device), MTL(currentFramebuffer())); + + return true; +} + +void MslMaterial::clearShader() +{ + _hwShader = nullptr; + _glProgram = nullptr; +} + +bool MslMaterial::generateShader(GenContext& context) +{ + if (!_elem) + { + return false; + } + + _hasTransparency = isTransparentSurface(_elem, context.getShaderGenerator().getTarget()); + + GenContext materialContext = context; + materialContext.getOptions().hwTransparency = _hasTransparency; + + // Initialize in case creation fails and throws an exception + clearShader(); + + _hwShader = createShader("Shader", materialContext, _elem); + if (!_hwShader) + { + return false; + } + + _glProgram = MslProgram::create(); + _glProgram->setStages(_hwShader); + _glProgram->build(MTL(device), MTL(currentFramebuffer())); + + return true; +} + +bool MslMaterial::generateShader(ShaderPtr hwShader) +{ + _hwShader = hwShader; + + _glProgram = MslProgram::create(); + _glProgram->setStages(hwShader); + _glProgram->build(MTL(device), MTL(currentFramebuffer())); + + return true; +} + +bool MslMaterial::bindShader() const +{ + if (_glProgram) + { + _glProgram->bind(MTL(renderCmdEncoder)); + return true; + } + return false; +} + +void MslMaterial::prepareUsedResources(CameraPtr cam, + GeometryHandlerPtr geometryHandler, + ImageHandlerPtr imageHandler, + LightHandlerPtr lightHandler) +{ + if (!_glProgram) + { + return; + } + + _glProgram->prepareUsedResources(MTL(renderCmdEncoder), + cam, geometryHandler, + imageHandler, + lightHandler); +} + +void MslMaterial::bindMesh(MeshPtr mesh) +{ + if (!mesh || !_glProgram) + { + return; + } + + _glProgram->bind(MTL(renderCmdEncoder)); + if (_boundMesh && mesh->getName() != _boundMesh->getName()) + { + _glProgram->unbindGeometry(); + } + _glProgram->bindMesh(MTL(renderCmdEncoder), mesh); + _boundMesh = mesh; +} + +bool MslMaterial::bindPartition(MeshPartitionPtr part) const +{ + if (!_glProgram) + { + return false; + } + + _glProgram->bind(MTL(renderCmdEncoder)); + _glProgram->bindPartition(part); + + return true; +} + +void MslMaterial::bindViewInformation(CameraPtr camera) +{ + if (!_glProgram) + { + return; + } + + _glProgram->bindViewInformation(camera); +} + +void MslMaterial::unbindImages(ImageHandlerPtr imageHandler) +{ + for (ImagePtr image : _boundImages) + { + imageHandler->unbindImage(image); + } +} + +void MslMaterial::bindImages(ImageHandlerPtr imageHandler, const FileSearchPath& searchPath, bool enableMipmaps) +{ + if (!_glProgram) + { + return; + } + + _boundImages.clear(); + + const VariableBlock* publicUniforms = getPublicUniforms(); + if (!publicUniforms) + { + return; + } + for (const auto& uniform : publicUniforms->getVariableOrder()) + { + if (uniform->getType() != Type::FILENAME) + { + continue; + } + const std::string& uniformVariable = uniform->getVariable(); + std::string filename; + if (uniform->getValue()) + { + filename = searchPath.find(uniform->getValue()->getValueString()); + } + + // Extract out sampling properties + ImageSamplingProperties samplingProperties; + samplingProperties.setProperties(uniformVariable, *publicUniforms); + + // Set the requested mipmap sampling property, + samplingProperties.enableMipmaps = enableMipmaps; + + ImagePtr image = bindImage(filename, uniformVariable, imageHandler, samplingProperties); + if (image) + { + _boundImages.push_back(image); + } + } +} + +ImagePtr MslMaterial::bindImage(const FilePath& filePath, + const std::string& uniformName, + ImageHandlerPtr imageHandler, + const ImageSamplingProperties& samplingProperties) +{ + if (!_glProgram) + { + return nullptr; + } + + // Create a filename resolver for geometric properties. + StringResolverPtr resolver = StringResolver::create(); + if (!getUdim().empty()) + { + resolver->setUdimString(getUdim()); + } + imageHandler->setFilenameResolver(resolver); + + // Acquire the given image. + return imageHandler->acquireImage(filePath); +} + +void MslMaterial::bindLighting(LightHandlerPtr lightHandler, + ImageHandlerPtr imageHandler, + const ShadowState& shadowState) +{ + if (!_glProgram) + { + return; + } + + // Bind environment and local lighting. + _glProgram->bindLighting(lightHandler, imageHandler); + + // Bind shadow map properties + if (shadowState.shadowMap && _glProgram->hasUniform(TEXTURE_NAME(HW::SHADOW_MAP))) + { + ImageSamplingProperties samplingProperties; + samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; + + // Bind the shadow map. + _glProgram->bindTexture(imageHandler, TEXTURE_NAME(HW::SHADOW_MAP), shadowState.shadowMap, samplingProperties); + _glProgram->bindUniform(HW::SHADOW_MATRIX, Value::createValue(shadowState.shadowMatrix)); + } + + // Bind ambient occlusion properties. + if (shadowState.ambientOcclusionMap && _glProgram->hasUniform(TEXTURE_NAME(HW::AMB_OCC_MAP))) + { + ImageSamplingProperties samplingProperties; + samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::PERIODIC; + samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::PERIODIC; + samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; + + // Bind the ambient occlusion map. + _glProgram->bindTexture(imageHandler, TEXTURE_NAME(HW::AMB_OCC_MAP), + shadowState.ambientOcclusionMap, + samplingProperties); + + _glProgram->bindUniform(HW::AMB_OCC_GAIN, Value::createValue(shadowState.ambientOcclusionGain)); + } +} + +void MslMaterial::bindUnits(UnitConverterRegistryPtr& registry, const GenContext& context) +{ + if (!bindShader()) + { + return; + } + + ShaderPort* port = nullptr; + VariableBlock* publicUniforms = getPublicUniforms(); + if (publicUniforms) + { + // Scan block based on unit name match predicate + port = publicUniforms->find( + [](ShaderPort* port) + { + return (port && (port->getName() == DISTANCE_UNIT_TARGET_NAME)); + }); + + // Check if the uniform exists in the shader program + if (port && !_glProgram->getUniformsList().count(port->getVariable())) + { + port = nullptr; + } + } + + if (port) + { + int intPortValue = registry->getUnitAsInteger(context.getOptions().targetDistanceUnit); + if (intPortValue >= 0) + { + port->setValue(Value::createValue(intPortValue)); + if (_glProgram->hasUniform(DISTANCE_UNIT_TARGET_NAME)) + { + _glProgram->bindUniform(DISTANCE_UNIT_TARGET_NAME, Value::createValue(intPortValue)); + } + } + } +} + +void MslMaterial::drawPartition(MeshPartitionPtr part) const +{ + if (!part || !bindPartition(part)) + { + return; + } + MeshIndexBuffer& indexData = part->getIndices(); + + [MTL(renderCmdEncoder) drawIndexedPrimitives:MTLPrimitiveTypeTriangle + indexCount:indexData.size() + indexType:MTLIndexTypeUInt32 + indexBuffer:_glProgram->getIndexBuffer(part) + indexBufferOffset:0]; +} + +void MslMaterial::unbindGeometry() +{ + if (_glProgram) + { + _glProgram->unbindGeometry(); + } + _boundMesh = nullptr; +} + +VariableBlock* MslMaterial::getPublicUniforms() const +{ + if (!_hwShader) + { + return nullptr; + } + + ShaderStage& stage = _hwShader->getStage(Stage::PIXEL); + VariableBlock& block = stage.getUniformBlock(HW::PUBLIC_UNIFORMS); + + return █ +} + +ShaderPort* MslMaterial::findUniform(const std::string& path) const +{ + ShaderPort* port = nullptr; + VariableBlock* publicUniforms = getPublicUniforms(); + if (publicUniforms) + { + // Scan block based on path match predicate + port = publicUniforms->find( + [path](ShaderPort* port) + { + return (port && stringEndsWith(port->getPath(), path)); + }); + + // Check if the uniform exists in the shader program + if (port && !_glProgram->getUniformsList().count( + publicUniforms->getInstance() + "." + + port->getVariable())) + { + port = nullptr; + } + } + return port; +} + +void MslMaterial::modifyUniform(const std::string& path, ConstValuePtr value, std::string valueString) +{ + ShaderPort* uniform = findUniform(path); + if (!uniform) + { + return; + } + + _glProgram->bindUniform(uniform->getVariable(), value); + + if (valueString.empty()) + { + valueString = value->getValueString(); + } + uniform->setValue(Value::createValueFromStrings(valueString, uniform->getType()->getName())); + if (_doc) + { + ElementPtr element = _doc->getDescendant(uniform->getPath()); + if (element) + { + ValueElementPtr valueElement = element->asA(); + if (valueElement) + { + valueElement->setValueString(valueString); + } + } + } +} + +MATERIALX_NAMESPACE_END + diff --git a/source/MaterialXRenderMsl/MslPipelineStateObject.h b/source/MaterialXRenderMsl/MslPipelineStateObject.h new file mode 100644 index 0000000000..234a0eb850 --- /dev/null +++ b/source/MaterialXRenderMsl/MslPipelineStateObject.h @@ -0,0 +1,321 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_MSLPROGRAM_H +#define MATERIALX_MSLPROGRAM_H + +/// @file +/// MSL Program interfaces + +#include + +#include +#include +#include +#include + +#include + +#import + +MATERIALX_NAMESPACE_BEGIN + +// Shared pointer to a MslProgram +using MslProgramPtr = std::shared_ptr; +using MetalFramebufferPtr = std::shared_ptr; + +/// @class MslProgram +/// A class representing an executable MSL program. +/// +/// There are two main interfaces which can be used. One which takes in a HwShader and one which +/// allows for explicit setting of shader stage code. +class MX_RENDERMSL_API MslProgram +{ + public: + /// Create a MSL program instance + static MslProgramPtr create() + { + return MslProgramPtr(new MslProgram()); + } + + /// Destructor + virtual ~MslProgram(); + + /// @name Shader code setup + /// @{ + + /// Set up code stages to validate based on an input hardware shader. + /// @param shader Hardware shader to use + void setStages(ShaderPtr shader); + + /// Set the code stages based on a list of stage strings. + /// Refer to the ordering of stages as defined by a HwShader. + /// @param stage Name of the shader stage. + /// @param sourceCode Source code of the shader stage. + void addStage(const string& stage, const string& sourceCode); + + /// Get source code string for a given stage. + /// @return Shader stage string. String is empty if not found. + const string& getStageSourceCode(const string& stage) const; + + /// Clear out any existing stages + void clearStages(); + + /// Return the shader, if any, used to generate this program. + ShaderPtr getShader() const + { + return _shader; + } + + /// @} + /// @name Program validation and introspection + /// @{ + + /// Create the pipeline state object from stages specified + /// @param device MetalDevice that pipeline state object is being created on. + /// @param framebuffer specifies information about output frame buffer. + /// An exception is thrown if the program cannot be created. + /// The exception will contain a list of program creation errors. + /// @return Pipeline State Object identifier. + id build(id device, MetalFramebufferPtr frameBuffer); + + /// Structure to hold information about program inputs. + /// The structure is populated by directly scanning the program so may not contain + /// some inputs listed on any associated HwShader as those inputs may have been + /// optimized out if they are unused. + struct MX_RENDERMSL_API Input + { + static int INVALID_METAL_TYPE; + + /// Program location. -1 means an invalid location + int location; + /// Metal type of the input. -1 means an invalid type + int resourceType; + /// Size. + int size; + /// Input type string. Will only be non-empty if initialized stages with a HwShader + string typeString; + /// Input value. Will only be non-empty if initialized stages with a HwShader and a value was set during + /// shader generation. + MaterialX::ValuePtr value; + /// Is this a constant + bool isConstant; + /// Element path (if any) + string path; + /// Unit + string unit; + /// Colorspace + string colorspace; + + /// Program input constructor + Input(int inputLocation, int inputType, int inputSize, const string& inputPath) : + location(inputLocation), + resourceType(inputType), + size(inputSize), + isConstant(false), + path(inputPath) + { } + }; + /// Program input structure shared pointer type + using InputPtr = std::shared_ptr; + /// Program input shaded pointer map type + using InputMap = std::unordered_map; + + /// Get list of program input uniforms. + /// The program must have been created successfully first. + /// An exception is thrown if the parsing of the program for uniforms cannot be performed. + /// @return Program uniforms list. + const InputMap& getUniformsList(); + + /// Get list of program input attributes. + /// The program must have been created successfully first. + /// An exception is thrown if the parsing of the program for attribute cannot be performed. + /// @return Program attributes list. + const InputMap& getAttributesList(); + + /// Find the locations in the program which starts with a given variable name + /// @param variable Variable to search for + /// @param variableList List of program inputs to search + /// @param foundList Returned list of found program inputs. Empty if none found. + /// @param exactMatch Search for exact variable name match. + void findInputs(const string& variable, + const InputMap& variableList, + InputMap& foundList, + bool exactMatch); + + /// @} + /// @name Program activation + /// @{ + + /// Bind the pipeline state object to the command encoder. + /// @param renderCmdEncoder encoder that binds the pipeline state object. + /// @return False if failed + bool bind(id renderCmdEncoder); + + /// Bind inputs + /// @param renderCmdEncoder encoder that inputs will be bound to. + /// @param cam Camera object use to view the object + /// @param geometryHandler + /// @param imageHandler + /// @param lightHandler + /// @return void - No return value + void prepareUsedResources(id renderCmdEncoder, + CameraPtr cam, + GeometryHandlerPtr geometryHandler, + ImageHandlerPtr imageHandler, + LightHandlerPtr lightHandler); + + /// Return true if a uniform with the given name is present. + bool hasUniform(const string& name); + + /// Bind a value to the uniform with the given name. + void bindUniform(const string& name, ConstValuePtr value, bool errorIfMissing = true); + + /// Bind attribute buffers to attribute inputs. + /// A hardware buffer of the given attribute type is created and bound to the program locations + /// for the input attribute. + /// @param renderCmdEncoder Metal Render Command Encoder that the attribute being bind to + /// @param inputs Attribute inputs to bind to + /// @param mesh Mesh containing streams to bind + void bindAttribute(id renderCmdEncoder, + const MslProgram::InputMap& inputs, + MeshPtr mesh); + + /// Bind input geometry partition (indexing) + void bindPartition(MeshPartitionPtr partition); + + /// Bind input geometry streams + void bindMesh(id renderCmdEncoder, MeshPtr mesh); + + /// Queries the index buffer assinged to a mesh partition + id getIndexBuffer(MeshPartitionPtr mesh) { + if(_indexBufferIds.find(mesh) != _indexBufferIds.end()) + return _indexBufferIds[mesh]; + return nil; + } + + + /// Unbind any bound geometry + void unbindGeometry(); + + /// Bind any input textures + void bindTextures(id renderCmdEncoder, + LightHandlerPtr lightHandler, + ImageHandlerPtr imageHandler); + + void bindTexture(ImageHandlerPtr imageHandler, + string shaderTextureName, + ImagePtr imagePtr, + ImageSamplingProperties samplingProperties); + + /// Bind lighting + void bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr imageHandler); + + /// Bind view information + void bindViewInformation(CameraPtr camera); + + /// Bind time and frame + void bindTimeAndFrame(float time = 1.0f, float frame = 1.0f); + + /// @} + /// @name Utilities + /// @{ + + /// Returns if alpha blending is enabled. + bool isTransparent() const { return _alphaBlendingEnabled; } + + /// Print all uniforms to the given stream. + void printUniforms(std::ostream& outputStream); + + /// Print all attributes to the given stream. + void printAttributes(std::ostream& outputStream); + + /// @} + + public: + static unsigned int UNDEFINED_METAL_RESOURCE_ID; + static int UNDEFINED_METAL_PROGRAM_LOCATION; + + protected: + MslProgram(); + + // Update a list of program input uniforms + const InputMap& updateUniformsList(); + + // Update a list of program input attributes + const InputMap& updateAttributesList(); + + // Clear out any cached input lists + void clearInputLists(); + + // Utility to find a uniform value in an uniform list. + // If uniform cannot be found a null pointer will be return. + ValuePtr findUniformValue(const string& uniformName, const InputMap& uniformList); + + // Bind an individual texture to a program uniform location + ImagePtr bindTexture(id renderCmdEncoder, + unsigned int uniformLocation, + const FilePath& filePath, + ImageSamplingProperties samplingProperties, + ImageHandlerPtr imageHandler); + + // Bind an individual texture to a program uniform location + ImagePtr bindTexture(id renderCmdEncoder, + unsigned int uniformLocation, + ImagePtr imagePtr, + ImageHandlerPtr imageHandler); + + void bindUniformBuffers(id renderCmdEncoder, + LightHandlerPtr lightHandler, + CameraPtr camera); + + // Delete any currently created pso + void reset(); + + // Utility to map a MaterialX type to an METAL type + static MTLDataType mapTypeToMetalType(const TypeDesc* type); + + private: + // Stages used to create program + // Map of stage name and its source code + StringMap _stages; + + // Generated pipeline state object. A non-zero number indicates a valid shader program. + id _pso = nil; + MTLRenderPipelineReflection* _psoReflection = nil; + + // List of program input uniforms + InputMap _uniformList; + std::unordered_map _globalUniformNameList; + // List of program input attributes + InputMap _attributeList; + + std::unordered_map _explicitBoundImages; + + // Hardware shader (if any) used for program creation + ShaderPtr _shader; + + // Attribute buffer resource handles + // for each attribute identifier in the program + std::unordered_map> _attributeBufferIds; + + // Attribute indexing buffer handle + std::map> _indexBufferIds; + + // Program texture map + std::unordered_map _programTextures; + + // Metal Device Object + id _device = nil; + + bool _alphaBlendingEnabled = false; + + float _time = 0.0f; + float _frame = 0.0f; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRenderMsl/MslPipelineStateObject.mm b/source/MaterialXRenderMsl/MslPipelineStateObject.mm new file mode 100644 index 0000000000..954ca2ec58 --- /dev/null +++ b/source/MaterialXRenderMsl/MslPipelineStateObject.mm @@ -0,0 +1,1593 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#import + +MATERIALX_NAMESPACE_BEGIN + +namespace +{ + +const float PI = std::acos(-1.0f); + +} // anonymous namespace + +// Metal Constants +unsigned int MslProgram::UNDEFINED_METAL_RESOURCE_ID = 0; +int MslProgram::UNDEFINED_METAL_PROGRAM_LOCATION = -1; +int MslProgram::Input::INVALID_METAL_TYPE = -1; + +// +// MslProgram methods +// + +MslProgram::MslProgram() : + _pso(nil), + _psoReflection(nil), + _shader(nullptr), + _alphaBlendingEnabled(false) +{ +} + +MslProgram::~MslProgram() +{ + reset(); +} + +void MslProgram::setStages(ShaderPtr shader) +{ + if (!shader) + { + throw ExceptionRenderError("Cannot set stages using null hardware shader"); + } + + // Clear out any old data + clearStages(); + + // Extract out the shader code per stage + _shader = shader; + for (size_t i =0; inumStages(); ++i) + { + const ShaderStage& stage = shader->getStage(i); + addStage(stage.getName(), stage.getSourceCode()); + } + + // A stage change invalidates any cached parsed inputs + clearInputLists(); +} + +void MslProgram::addStage(const string& stage, const string& sourceCode) +{ + _stages[stage] = sourceCode; +} + +const string& MslProgram::getStageSourceCode(const string& stage) const +{ + auto it = _stages.find(stage); + if (it != _stages.end()) + { + return it->second; + } + return EMPTY_STRING; +} + +void MslProgram::clearStages() +{ + _stages.clear(); + + // Clearing stages invalidates any cached inputs + clearInputLists(); +} + +MTLVertexFormat GetMetalFormatFromMetalType(MTLDataType type) +{ + switch(type) + { + case MTLDataTypeFloat : return MTLVertexFormatFloat; + case MTLDataTypeFloat2: return MTLVertexFormatFloat2; + case MTLDataTypeFloat3: return MTLVertexFormatFloat3; + case MTLDataTypeFloat4: return MTLVertexFormatFloat4; + case MTLDataTypeInt: return MTLVertexFormatInt; + case MTLDataTypeInt2: return MTLVertexFormatInt2; + case MTLDataTypeInt3: return MTLVertexFormatInt3; + case MTLDataTypeInt4: return MTLVertexFormatInt4; + default: return MTLVertexFormatInvalid; + } + return MTLVertexFormatInt; +} + +int GetStrideOfMetalType(MTLDataType type) +{ + switch(type) + { + case MTLDataTypeInt: + case MTLDataTypeFloat : return 1 * 4; + case MTLDataTypeInt2: + case MTLDataTypeFloat2: return 2 * 4; + case MTLDataTypeInt3: + case MTLDataTypeFloat3: return 3 * 4; + case MTLDataTypeInt4: + case MTLDataTypeFloat4: return 4 * 4; + default: return 0; + } + + return 0; +} + +id MslProgram::build(id device, MetalFramebufferPtr framebuffer) +{ + StringVec errors; + const string errorType("MSL program creation error."); + + NSError* error = nil; + + reset(); + + _device = device; + + unsigned int stagesBuilt = 0; + unsigned int desiredStages = 0; + for (const auto& it : _stages) + { + if (it.second.length()) + desiredStages++; + } + + MTLCompileOptions* options = [MTLCompileOptions new]; +#ifdef MAC_OS_VERSION_11_0 + if (@available(macOS 11.0, ios 14.0, *)) + options.languageVersion = MTLLanguageVersion2_3; + else +#endif + options.languageVersion = MTLLanguageVersion2_0; + options.fastMathEnabled = true; + + // Create vertex shader + id vertexShaderId = nil; + { + string &vertexShaderSource = _stages[Stage::VERTEX]; + if (vertexShaderSource.length() < 1) + { + errors.push_back("Vertex Shader is empty."); + return nil; + } + + NSString* sourcCode = [NSString stringWithUTF8String:vertexShaderSource.c_str()]; + id library = [device newLibraryWithSource:sourcCode + options:options + error:&error]; + + if(library == nil) + { + errors.push_back("Error in compiling vertex shader:"); + if(error) + { + errors.push_back([[error localizedDescription] UTF8String]); + } + return nil; + } + + vertexShaderId = [library newFunctionWithName:@"VertexMain"]; + + if(vertexShaderId) + { + stagesBuilt++; + } + } + + // Create fragment shader + string& fragmentShaderSource = _stages[Stage::PIXEL]; + if (fragmentShaderSource.length() < 1) + { + errors.push_back("Fragment Shader is empty."); + return nil; + } + + // Fragment shader compilation code + id fragmentShaderId = nil; + { + NSString* sourcCode = [NSString stringWithUTF8String:fragmentShaderSource.c_str()]; + id library = [device newLibraryWithSource:sourcCode options:options error:&error]; + if(!library) + { + errors.push_back("Error in compiling fragment shader:"); + if(error) + { + std::string libCompilationError = [[error localizedDescription] UTF8String]; + printf("Compilation Errors:%s\n", libCompilationError.c_str()); + errors.push_back(libCompilationError); + return nil; + } + } + + fragmentShaderId = [library newFunctionWithName:@"FragmentMain"]; + assert(fragmentShaderId); + + if(library) + { + stagesBuilt++; + } + } + + + // Link stages to a programs + if (stagesBuilt == desiredStages) + { + MTLRenderPipelineDescriptor* psoDesc = [MTLRenderPipelineDescriptor new]; + psoDesc.vertexFunction = vertexShaderId; + psoDesc.fragmentFunction = fragmentShaderId; + psoDesc.colorAttachments[0].pixelFormat = framebuffer->getColorTexture().pixelFormat; + psoDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float; + + if (_shader->hasAttribute(HW::ATTR_TRANSPARENT)) + { + psoDesc.colorAttachments[0].blendingEnabled = YES; + psoDesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + psoDesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + psoDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + psoDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; + psoDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + psoDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + + _alphaBlendingEnabled = true; + } + + MTLVertexDescriptor *vd = [MTLVertexDescriptor new]; + + for( int i = 0; i < vertexShaderId.vertexAttributes.count; ++i) + { + MTLVertexAttribute* vertexAttrib = vertexShaderId.vertexAttributes[i]; + + vd.attributes[i].bufferIndex = i; + vd.attributes[i].format = GetMetalFormatFromMetalType(vertexAttrib.attributeType); + vd.attributes[i].offset = 0; + + InputPtr inputPtr = std::make_shared(vertexAttrib.attributeIndex, vertexAttrib.attributeType, GetStrideOfMetalType(vertexAttrib.attributeType), EMPTY_STRING); + // Attempt to pull out the set number for specific attributes + // + string sattributeName(vertexAttrib.name.UTF8String); + const string colorSet(HW::IN_COLOR + "_"); + const string uvSet(HW::IN_TEXCOORD + "_"); + if (string::npos != sattributeName.find(colorSet)) + { + string setNumber = sattributeName.substr(colorSet.size(), sattributeName.size()); + inputPtr->value = Value::createValueFromStrings(setNumber, getTypeString()); + } + else if (string::npos != sattributeName.find(uvSet)) + { + string setNumber = sattributeName.substr(uvSet.size(), sattributeName.size()); + inputPtr->value = Value::createValueFromStrings(setNumber, getTypeString()); + } + + _attributeList[sattributeName] = inputPtr; + + vd.layouts[i].stride = GetStrideOfMetalType(vertexAttrib.attributeType);; + vd.layouts[i].stepFunction = MTLVertexStepFunctionPerVertex; + + } + + psoDesc.vertexDescriptor = vd; + + _pso = [device newRenderPipelineStateWithDescriptor:psoDesc + options:MTLPipelineOptionArgumentInfo|MTLPipelineOptionBufferTypeInfo + reflection:&_psoReflection error:&error]; + + [_pso retain]; + [_psoReflection retain]; + + + if(error) + { + errors.push_back("Error in linking program:"); + errors.push_back([[error localizedDescription] UTF8String]); + } + } + else + { + errors.push_back("Failed to build all stages."); + throw ExceptionRenderError(errorType, errors); + } + + // If we encountered any errors while trying to create return list + // of all errors. That is we collect all errors per stage plus any + // errors during linking and throw one exception for them all so that + // if there is a failure a complete set of issues is returned. We do + // this after cleanup so keep //gl state clean. + if (!errors.empty()) + { + throw ExceptionRenderError(errorType, errors); + } + + return _pso; +} + +bool MslProgram::bind(id renderCmdEncoder) +{ + if (_pso != nil) + { + [renderCmdEncoder setRenderPipelineState:_pso]; + return true; + } + return false; +} + +void MslProgram::prepareUsedResources(id renderCmdEncoder, + CameraPtr cam, + GeometryHandlerPtr geometryHandler, + ImageHandlerPtr imageHandler, + LightHandlerPtr lightHandler) +{ + // Bind the program to use + if (!bind(renderCmdEncoder)) + { + const string errorType("MSL bind inputs error."); + StringVec errors; + errors.push_back("Cannot bind inputs without a valid program"); + throw ExceptionRenderError(errorType, errors); + } + + // Parse for uniforms and attributes + getUniformsList(); + getAttributesList(); + + // Bind based on inputs found + bindViewInformation(cam); + if(geometryHandler) + { + for (const auto& mesh : geometryHandler->getMeshes()) + { + bindMesh(renderCmdEncoder, mesh); + } + } + + bindTimeAndFrame(); + bindLighting(lightHandler, imageHandler); + bindTextures(renderCmdEncoder, lightHandler, imageHandler); + bindUniformBuffers(renderCmdEncoder, lightHandler, cam); +} + +void MslProgram::bindAttribute(id renderCmdEncoder, const MslProgram::InputMap& inputs, MeshPtr mesh) +{ + const string errorType("MSL bind attribute error."); + StringVec errors; + + if (!mesh) + { + errors.push_back("No geometry set to bind"); + throw ExceptionRenderError(errorType, errors); + } + + const size_t FLOAT_SIZE = sizeof(float); + + for (const auto& input : inputs) + { + int location = input.second->location; + unsigned int index = input.second->value ? input.second->value->asA() : 0; + + unsigned int stride = 0; + MeshStreamPtr stream = mesh->getStream(input.first); + if (!stream) + { + errors.push_back("Geometry buffer could not be retrieved for binding: " + input.first + ". Index: " + std::to_string(index)); + throw ExceptionRenderError(errorType, errors); + } + MeshFloatBuffer& attributeData = stream->getData(); + stride = stream->getStride(); + + if (attributeData.empty() || (stride == 0)) + { + errors.push_back("Geometry buffer could not be retrieved for binding: " + input.first + ". Index: " + std::to_string(index)); + throw ExceptionRenderError(errorType, errors); + } + + if (_attributeBufferIds.find(input.first) == _attributeBufferIds.end()) + { + std::vector restructuredData; + const void* bufferData = nullptr; + size_t bufferSize = 0; + + int shaderStride = input.second->size / FLOAT_SIZE; + + if(shaderStride == stride) + { + bufferData = &attributeData[0]; + bufferSize = attributeData.size()*FLOAT_SIZE; + } + else + { + size_t nElements = attributeData.size()/stride; + bufferSize = nElements*shaderStride*FLOAT_SIZE; + restructuredData.resize(bufferSize, 0.0f); + size_t j = 0; + for(int i = 0; i < nElements; ++i) + { + memcpy(&restructuredData[j], + &attributeData[i*stride], + stride*FLOAT_SIZE); + j += shaderStride * FLOAT_SIZE; + } + bufferData = &restructuredData[0]; + } + + // Create a buffer based on attribute type. + id buffer = [_device newBufferWithBytes:bufferData length:bufferSize options:MTLResourceStorageModeShared]; + _attributeBufferIds[input.first] = buffer; + } + + [renderCmdEncoder setVertexBuffer:_attributeBufferIds[input.first] offset:0 atIndex:location]; + } +} + +void MslProgram::bindPartition(MeshPartitionPtr part) +{ + StringVec errors; + const string errorType("MSL geometry bind error."); + if (!part || part->getFaceCount() == 0) + { + errors.push_back("Cannot bind geometry partition"); + throw ExceptionRenderError(errorType, errors); + } + + if (_indexBufferIds.find(part) == _indexBufferIds.end()) + { + MeshIndexBuffer& indexData = part->getIndices(); + size_t indexBufferSize = indexData.size(); + id indexBuffer = [_device newBufferWithBytes:&indexData[0] length:indexBufferSize * sizeof(uint32_t) options:MTLStorageModeShared]; + _indexBufferIds[part] = indexBuffer; + } +} + +void MslProgram::bindMesh(id renderCmdEncoder, MeshPtr mesh) +{ + StringVec errors; + const string errorType("MSL geometry bind error."); + + if (_pso == nil) + { + errors.push_back("Cannot bind geometry without a valid program"); + throw ExceptionRenderError(errorType, errors); + } + if (!mesh) + { + errors.push_back("No mesh to bind"); + throw ExceptionRenderError(errorType, errors); + } + + MslProgram::InputMap foundList; + const MslProgram::InputMap& attributeList = getAttributesList(); + + // Bind positions + findInputs(HW::IN_POSITION, attributeList, foundList, true); + if (foundList.size()) + { + bindAttribute(renderCmdEncoder, foundList, mesh); + } + + // Bind normals + findInputs(HW::IN_NORMAL, attributeList, foundList, true); + if (foundList.size()) + { + bindAttribute(renderCmdEncoder, foundList, mesh); + } + + // Bind tangents + findInputs(HW::IN_TANGENT, attributeList, foundList, true); + if (foundList.size()) + { + bindAttribute(renderCmdEncoder, foundList, mesh); + } + + // Bind colors + // Search for anything that starts with the color prefix + findInputs(HW::IN_COLOR + "_", attributeList, foundList, false); + if (foundList.size()) + { + bindAttribute(renderCmdEncoder, foundList, mesh); + } + + // Bind texture coordinates + // Search for anything that starts with the texcoord prefix + findInputs(HW::IN_TEXCOORD + "_", attributeList, foundList, false); + if (foundList.size()) + { + bindAttribute(renderCmdEncoder, foundList, mesh); + } + + // Bind any named varying geometric property information + findInputs(HW::IN_GEOMPROP + "_", attributeList, foundList, false); + if (foundList.size()) + { + bindAttribute(renderCmdEncoder, foundList, mesh); + } + + // Bind any named uniform geometric property information + const MslProgram::InputMap& uniformList = getUniformsList(); + findInputs(HW::GEOMPROP + "_", uniformList, foundList, false); +} + +void MslProgram::unbindGeometry() +{ + // Clean up buffers + // + for (const auto& attributeBufferId : _attributeBufferIds) + { + [attributeBufferId.second release]; + } + _attributeBufferIds.clear(); + for (const auto& indexBufferId : _indexBufferIds) + { + [indexBufferId.second release]; + } + _indexBufferIds.clear(); +} + +ImagePtr MslProgram::bindTexture(id renderCmdEncoder, + unsigned int uniformLocation, + const FilePath& filePath, + ImageSamplingProperties samplingProperties, + ImageHandlerPtr imageHandler) +{ + // Acquire the image. + string error; + ImagePtr image = imageHandler->acquireImage(filePath); + imageHandler->bindImage(image, samplingProperties); + return bindTexture(renderCmdEncoder, uniformLocation, image, imageHandler); +} + +ImagePtr MslProgram::bindTexture(id renderCmdEncoder, + unsigned int uniformLocation, + ImagePtr image, + ImageHandlerPtr imageHandler) +{ + // Acquire the image. + string error; + if (static_cast(imageHandler.get())->bindImage( + renderCmdEncoder, uniformLocation, image)) + { + return image; + } + return nullptr; +} + +MaterialX::ValuePtr MslProgram::findUniformValue( + const string& uniformName, + const MslProgram::InputMap& uniformList) +{ + auto uniform = uniformList.find(uniformName); + if (uniform != uniformList.end()) + { + int location = uniform->second->location; + if (location >= 0) + { + return uniform->second->value; + } + } + return nullptr; +} + +void MslProgram::bindTextures(id renderCmdEncoder, + LightHandlerPtr lightHandler, + ImageHandlerPtr imageHandler) +{ + const VariableBlock& publicUniforms = _shader->getStage(Stage::PIXEL).getUniformBlock(HW::PUBLIC_UNIFORMS); + for (MTLArgument *arg in _psoReflection.fragmentArguments) + { + if (arg.type == MTLArgumentTypeTexture) + { + bool found = false; + + if(lightHandler) + { + // Bind environment lights. + ImageMap envLights = + { + { HW::ENV_RADIANCE, lightHandler->getEnvRadianceMap() }, + { HW::ENV_IRRADIANCE, lightHandler->getEnvIrradianceMap() } + }; + for (const auto& env : envLights) + { + std::string str(arg.name.UTF8String); + size_t loc = str.find(env.first); + if(loc != std::string::npos && env.second) + { + ImageSamplingProperties samplingProperties; + samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::PERIODIC; + samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; + + static_cast + (imageHandler.get())->bindImage(env.second, samplingProperties); + bindTexture(renderCmdEncoder, (unsigned int)arg.index, env.second, imageHandler); + found = true; + } + + } + } + + if(!found) + { + ImagePtr image = nullptr; + if(_explicitBoundImages.find(arg.name.UTF8String) != _explicitBoundImages.end()) + { + image = _explicitBoundImages[arg.name.UTF8String]; + } + + if(image && (image->getWidth() > 1 || image->getHeight() > 1)) + { + bindTexture(renderCmdEncoder, (unsigned int)arg.index, image, imageHandler); + found = true; + } + } + + if(!found) + { + auto uniform = _uniformList.find(arg.name.UTF8String); + if(uniform != _uniformList.end()) + { + string fileName = uniform->second->value ? uniform->second->value->getValueString() : ""; + ImageSamplingProperties samplingProperties; + string uniformNameWithoutPostfix = uniform->first; + { + size_t pos = uniformNameWithoutPostfix.find_last_of(IMAGE_PROPERTY_SEPARATOR); + if(pos != std::string::npos) + uniformNameWithoutPostfix = uniformNameWithoutPostfix.substr(0, pos); + } + samplingProperties.setProperties(uniformNameWithoutPostfix, publicUniforms); + bindTexture(renderCmdEncoder, (unsigned int)arg.index, fileName, samplingProperties, imageHandler); + } + } + } + } +} + +void MslProgram::bindTexture(ImageHandlerPtr imageHandler, + string shaderTextureName, + ImagePtr imagePtr, + ImageSamplingProperties samplingProperties) +{ + if(imageHandler->bindImage(imagePtr, samplingProperties)) + { + _explicitBoundImages[shaderTextureName] = imagePtr; + } +} + +void MslProgram::bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr imageHandler) +{ + if (!lightHandler) + { + // Nothing to bind if a light handler is not used. + // This is a valid condition for shaders that don't + // need lighting so just ignore silently. + return; + } + + StringVec errors; + + if (_pso == nil) + { + const string errorType("MSL light binding error."); + errors.push_back("Cannot bind without a valid program"); + throw ExceptionRenderError(errorType, errors); + } + + const MslProgram::InputMap& uniformList = getUniformsList(); + + // Set the number of active light sources + size_t lightCount = lightHandler->getLightSources().size(); + auto input = uniformList.find(HW::NUM_ACTIVE_LIGHT_SOURCES); + if (input == uniformList.end()) + { + // No lighting information so nothing further to do + lightCount = 0; + } + + if (lightCount == 0 && + !lightHandler->getEnvRadianceMap() && + !lightHandler->getEnvIrradianceMap()) + { + return; + } + + // Bind environment lights. + Matrix44 envRotation = Matrix44::createRotationY(PI) * lightHandler->getLightTransform().getTranspose(); + bindUniform(HW::ENV_MATRIX, Value::createValue(envRotation), false); + bindUniform(HW::ENV_RADIANCE_SAMPLES, Value::createValue(lightHandler->getEnvSampleCount()), false); + ImageMap envLights = + { + { HW::ENV_RADIANCE, lightHandler->getEnvRadianceMap() }, + { HW::ENV_IRRADIANCE, lightHandler->getEnvIrradianceMap() } + }; + for (const auto& env : envLights) + { + auto iblUniform = uniformList.find(TEXTURE_NAME(env.first)); + MslProgram::InputPtr inputPtr = iblUniform != uniformList.end() ? iblUniform->second : nullptr; + if (inputPtr) + { + ImagePtr image; + if (inputPtr->value) + { + string filename = inputPtr->value->getValueString(); + if (!filename.empty()) + { + image = imageHandler->acquireImage(filename); + } + } + if (!image) + { + image = env.second; + } + + if (image) + { + ImageSamplingProperties samplingProperties; + samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::PERIODIC; + samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; + imageHandler->bindImage(image, samplingProperties); + } + } + } + + // Bind direct lighting properties. + if (hasUniform(HW::NUM_ACTIVE_LIGHT_SOURCES)) + { + int lightCount = lightHandler->getDirectLighting() ? (int) lightHandler->getLightSources().size() : 0; + bindUniform(HW::NUM_ACTIVE_LIGHT_SOURCES, Value::createValue(lightCount)); + LightIdMap idMap = lightHandler->computeLightIdMap(lightHandler->getLightSources()); + size_t index = 0; + for (NodePtr light : lightHandler->getLightSources()) + { + auto nodeDef = light->getNodeDef(); + if (!nodeDef) + { + continue; + } + + const std::string prefix = HW::LIGHT_DATA_INSTANCE + "[" + std::to_string(index) + "]"; + + // Set light type id + std::string lightType(prefix + ".type"); + if (hasUniform(lightType)) + { + unsigned int lightTypeValue = idMap[nodeDef->getName()]; + bindUniform(lightType, Value::createValue((int) lightTypeValue)); + } + + // Set all inputs + for (const auto& input : light->getInputs()) + { + // Make sure we have a value to set + if (input->hasValue()) + { + std::string inputName(prefix + "." + input->getName()); + if (hasUniform(inputName)) + { + if (input->getName() == "direction" && input->hasValue() && input->getValue()->isA()) + { + Vector3 dir = input->getValue()->asA(); + dir = lightHandler->getLightTransform().transformVector(dir); + bindUniform(inputName, Value::createValue(dir)); + } + else + { + bindUniform(inputName, input->getValue()); + } + } + } + } + + ++index; + } + } + + // Bind the directional albedo table, if needed. + ImagePtr albedoTable = lightHandler->getAlbedoTable(); + if (albedoTable && hasUniform(TEXTURE_NAME(HW::ALBEDO_TABLE))) + { + ImageSamplingProperties samplingProperties; + samplingProperties.uaddressMode = ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.vaddressMode = ImageSamplingProperties::AddressMode::CLAMP; + samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; + bindTexture(imageHandler, + TEXTURE_NAME(HW::ALBEDO_TABLE), + albedoTable, + samplingProperties); + } +} + +bool MslProgram::hasUniform(const string& name) +{ + const MslProgram::InputMap& uniformList = getUniformsList(); + if(uniformList.find(name) != uniformList.end()) + return true; + if(_globalUniformNameList.find(name) != _globalUniformNameList.end() && uniformList.find(_globalUniformNameList[name]) != uniformList.end()) + return true; + return false; +} + +void MslProgram::bindUniform(const string& name, ConstValuePtr value, bool errorIfMissing) +{ + const MslProgram::InputMap& uniformList = getUniformsList(); + auto input = uniformList.find(name); + if (input != uniformList.end()) + { + _uniformList[name]->value = value->copy(); + } + else + { + auto globalNameMapping = _globalUniformNameList.find(name); + if(globalNameMapping != _globalUniformNameList.end()) + { + bindUniform(globalNameMapping->second, value, errorIfMissing); + } + else + { + if (errorIfMissing) + { + throw ExceptionRenderError("Unknown uniform: " + name); + } + return; + } + } +} + +void MslProgram::bindViewInformation(CameraPtr camera) +{ + StringVec errors; + const string errorType("MSL view input binding error."); + + if (_pso == nil) + { + errors.push_back("Cannot bind without a valid program"); + throw ExceptionRenderError(errorType, errors); + } + if (!camera) + { + errors.push_back("Cannot bind without a view handler"); + throw ExceptionRenderError(errorType, errors); + } +} + +void MslProgram::bindTimeAndFrame(float time, float frame) +{ + _time = time; + _frame = frame; +} + +void MslProgram::clearInputLists() +{ + _uniformList.clear(); + _globalUniformNameList.clear(); + _attributeList.clear(); + _attributeBufferIds.clear(); + _indexBufferIds.clear(); + _explicitBoundImages.clear(); +} + +const MslProgram::InputMap& MslProgram::getUniformsList() +{ + return updateUniformsList(); +} + +const MslProgram::InputMap& MslProgram::getAttributesList() +{ + return updateAttributesList(); +} + +const MslProgram::InputMap& MslProgram::updateUniformsList() +{ + StringVec errors; + const string errorType("MSL uniform parsing error."); + + if (_uniformList.size() > 0) + { + return _uniformList; + } + + if (_pso == nil) + { + errors.push_back("Cannot parse for uniforms without a valid program"); + throw ExceptionRenderError(errorType, errors); + } + + for (MTLArgument *arg in _psoReflection.vertexArguments) + { + if (arg.bufferDataType == MTLDataTypeStruct) + { + for (MTLStructMember* member in arg.bufferStructType.members) + { + InputPtr inputPtr = std::make_shared(arg.index, member.dataType, arg.bufferDataSize, EMPTY_STRING); + std::string memberName = member.name.UTF8String; + std::string uboDotMemberName = std::string(arg.name.UTF8String) + "." + member.name.UTF8String; + _uniformList[uboDotMemberName] = inputPtr; + _globalUniformNameList[member.name.UTF8String] = uboDotMemberName; + } + } + } + + for (MTLArgument *arg in _psoReflection.fragmentArguments) + { + if (arg.type == MTLArgumentTypeBuffer && arg.bufferDataType == MTLDataTypeStruct) + { + for (MTLStructMember* member in arg.bufferStructType.members) + { + std::string uboObjectName = std::string(arg.name.UTF8String); + std::string memberName = member.name.UTF8String; + std::string uboDotMemberName = uboObjectName + "." + memberName; + + InputPtr inputPtr = std::make_shared(arg.index, member.dataType, arg.bufferDataSize, EMPTY_STRING); + _uniformList[uboDotMemberName] = inputPtr; + _globalUniformNameList[memberName] = uboDotMemberName; + + if(MTLArrayType* arrayMember = member.arrayType) + { + for(int i = 0; i < arrayMember.arrayLength; ++i) + { + for (MTLStructMember* ArrayOfStructMember in arrayMember.elementStructType.members) + { + std::string memberNameDotSubmember = memberName + "[" + std::to_string(i) + "]." + ArrayOfStructMember.name.UTF8String; + std::string uboDotMemberNameDotSubmemberName = uboObjectName + "." + memberNameDotSubmember; + + InputPtr inputPtr = std::make_shared(ArrayOfStructMember.argumentIndex, ArrayOfStructMember.dataType, ArrayOfStructMember.offset, EMPTY_STRING); + _uniformList[uboDotMemberNameDotSubmemberName] = inputPtr; + _globalUniformNameList[memberNameDotSubmember] = uboDotMemberNameDotSubmemberName; + } + } + } + } + } + + if(arg.type == MTLArgumentTypeTexture) + { + if(HW::ENV_RADIANCE != arg.name.UTF8String && HW::ENV_IRRADIANCE != arg.name.UTF8String) + { + std::string texture_name = arg.name.UTF8String; + InputPtr inputPtr = std::make_shared(arg.index, 58, -1, EMPTY_STRING); + _uniformList[texture_name] = inputPtr; + } + } + } + + if (_shader) + { + // Check for any type mismatches between the program and the h/w shader. + // i.e the type indicated by the HwShader does not match what was generated. + bool uniformTypeMismatchFound = false; + + const ShaderStage& ps = _shader->getStage(Stage::PIXEL); + const ShaderStage& vs = _shader->getStage(Stage::VERTEX); + + // Process constants + const VariableBlock& constants = ps.getConstantBlock(); + for (size_t i=0; i< constants.size(); ++i) + { + const ShaderPort* v = constants[i]; + // There is no way to match with an unnamed variable + if (v->getVariable().empty()) + { + continue; + } + + // TODO: Shoud we really create new ones here each update? + InputPtr inputPtr = std::make_shared(-1, -1, int(v->getType()->getSize()), EMPTY_STRING); + _uniformList[v->getVariable()] = inputPtr; + inputPtr->isConstant = true; + inputPtr->value = v->getValue(); + inputPtr->typeString = v->getType()->getName(); + inputPtr->path = v->getPath(); + } + + // Process pixel stage uniforms + for (const auto& uniformMap : ps.getUniformBlocks()) + { + const VariableBlock& uniforms = *uniformMap.second; + if (uniforms.getName() == HW::LIGHT_DATA) + { + // Need to go through LightHandler to match with uniforms + continue; + } + + for (size_t i = 0; i < uniforms.size(); ++i) + { + const ShaderPort* v = uniforms[i]; + MTLDataType resourceType = mapTypeToMetalType(v->getType()); + + // There is no way to match with an unnamed variable + if (v->getVariable().empty()) + { + continue; + } + + // Ignore types which are unsupported in MSL. + if (resourceType == MTLDataTypeNone) + { + continue; + } + + int tries = 0; + auto inputIt = _uniformList.find(v->getVariable()); +try_again: if (inputIt != _uniformList.end()) + { + Input* input = inputIt->second.get(); + input->path = v->getPath(); + input->value = v->getValue(); + if (input->resourceType == resourceType) + { + input->typeString = v->getType()->getName(); + } + else + { + errors.push_back( + "Pixel shader uniform block type mismatch [" + uniforms.getName() + "]. " + + "Name: \"" + v->getVariable() + + "\". Type: \"" + v->getType()->getName() + + "\". Semantic: \"" + v->getSemantic() + + "\". Value: \"" + (v->getValue() ? v->getValue()->getValueString() : "") + + "\". resourceType: " + std::to_string(mapTypeToMetalType(v->getType())) + ); + uniformTypeMismatchFound = true; + } + } + else + { + if(tries == 0) + { + ++tries; + if(v->getType() == Type::FILENAME) + { + inputIt = _uniformList.find(TEXTURE_NAME(v->getVariable())); + } + else + { + inputIt = _uniformList.find(uniforms.getInstance() + "." + v->getVariable()); + } + goto try_again; + } + } + } + } + + // Process vertex stage uniforms + for (const auto& uniformMap : vs.getUniformBlocks()) + { + const VariableBlock& uniforms = *uniformMap.second; + for (size_t i = 0; i < uniforms.size(); ++i) + { + const ShaderPort* v = uniforms[i]; + auto inputIt = _uniformList.find(v->getVariable()); + if (inputIt != _uniformList.end()) + { + Input* input = inputIt->second.get(); + if (input->resourceType == mapTypeToMetalType(v->getType())) + { + input->typeString = v->getType()->getName(); + input->value = v->getValue(); + input->path = v->getPath(); + input->unit = v->getUnit(); + } + else + { + errors.push_back( + "Vertex shader uniform block type mismatch [" + uniforms.getName() + "]. " + + "Name: \"" + v->getVariable() + + "\". Type: \"" + v->getType()->getName() + + "\". Semantic: \"" + v->getSemantic() + + "\". Value: \"" + (v->getValue() ? v->getValue()->getValueString() : "") + + "\". Unit: \"" + (!v->getUnit().empty() ? v->getUnit() : "") + + "\". resourceType: " + std::to_string(mapTypeToMetalType(v->getType())) + ); + uniformTypeMismatchFound = true; + } + } + } + } + + // Throw an error if any type mismatches were found + if (uniformTypeMismatchFound) + { + throw ExceptionRenderError(errorType, errors); + } + } + + return _uniformList; +} + +void MslProgram::bindUniformBuffers(id renderCmdEncoder, LightHandlerPtr lightHandler, CameraPtr cam) +{ + auto setCommonUniform = [this](LightHandlerPtr lightHandler, CameraPtr cam, std::string uniformName, std::vector& data, size_t offset) -> bool + { + // View position and direction + if (uniformName == HW::VIEW_POSITION) + { + Matrix44 viewInverse = cam->getViewMatrix().getInverse(); + MaterialX::Vector3 viewPosition(viewInverse[3][0], viewInverse[3][1], viewInverse[3][2]); + memcpy((void*)&data[offset], viewPosition.data(), 3 * sizeof(float)); + return true; + } + if (uniformName == HW::VIEW_DIRECTION) + { + memcpy((void*)&data[offset], cam->getViewPosition().data(), 3 * sizeof(float)); + return true; + } + + // World matrix variants + const Matrix44& world = cam->getWorldMatrix(); + Matrix44 invWorld = world.getInverse(); + Matrix44 invTransWorld = invWorld.getTranspose(); + if (uniformName == HW::WORLD_MATRIX) + { + memcpy((void*)&data[offset], world.data(), 4 * 4 * sizeof(float)); + return false; + } + if (uniformName == HW::WORLD_TRANSPOSE_MATRIX) + { + memcpy((void*)&data[offset], world.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::WORLD_INVERSE_MATRIX) + { + memcpy((void*)&data[offset], invWorld.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::WORLD_INVERSE_TRANSPOSE_MATRIX) + { + memcpy((void*)&data[offset], invTransWorld.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + if(uniformName == HW::FRAME) + { + memcpy((void*)&data[offset], (const void*)&_frame, sizeof(float)); + return true; + } + if(uniformName == HW::TIME) + { + memcpy((void*)&data[offset], (const void*)&_time, sizeof(float)); + return true; + } + + + // Projection matrix variants + const Matrix44& proj = cam->getProjectionMatrix(); + if (uniformName == HW::PROJ_MATRIX) + { + memcpy((void*)&data[offset], proj.data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::PROJ_TRANSPOSE_MATRIX) + { + memcpy((void*)&data[offset], proj.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + + const Matrix44& projInverse= proj.getInverse(); + if (uniformName == HW::PROJ_INVERSE_MATRIX) + { + memcpy((void*)&data[offset], projInverse.data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::PROJ_INVERSE_TRANSPOSE_MATRIX) + { + memcpy((void*)&data[offset], projInverse.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + + // View matrix variants + const Matrix44& view = cam->getViewMatrix(); + Matrix44 viewInverse = view.getInverse(); + if (uniformName == HW::VIEW_MATRIX) + { + memcpy((void*)&data[offset], view.data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::VIEW_TRANSPOSE_MATRIX) + { + memcpy((void*)&data[offset], view.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::VIEW_INVERSE_MATRIX) + { + memcpy((void*)&data[offset], viewInverse.data(), 4 * 4 * sizeof(float)); + return true; + } + if (uniformName == HW::VIEW_INVERSE_TRANSPOSE_MATRIX) + { + memcpy((void*)&data[offset], viewInverse.getTranspose().data(), 4 * 4 * sizeof(float)); + return true; + } + + // View-projection matrix + const Matrix44& viewProj = view * proj; + if (uniformName == HW::VIEW_PROJECTION_MATRIX) + { + memcpy((void*)&data[offset], viewProj.data(), 4 * 4 * sizeof(float)); + return true; + } + + // View-projection-world matrix + const Matrix44& viewProjWorld = viewProj * world; + if (uniformName == HW::WORLD_VIEW_PROJECTION_MATRIX) + { + memcpy((void*)&data[offset], viewProjWorld.data(), 4 * 4 * sizeof(float)); + return true; + } + + if (uniformName == HW::ENV_RADIANCE_MIPS) + { + unsigned int maxMipCount = lightHandler->getEnvRadianceMap()->getMaxMipCount(); + memcpy((void*)&data[offset], &maxMipCount, sizeof(maxMipCount)); + return true; + } + + return false; + }; + + auto setValue = [](MaterialX::ValuePtr value, std::vector& data, size_t offset) + { + if (value->getTypeString() == "float") + { + float v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(float)); + } + else if (value->getTypeString() == "integer") + { + int v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(int)); + } + else if (value->getTypeString() == "boolean") + { + bool v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(bool)); + } + else if (value->getTypeString() == "color3") + { + Color3 v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(Color3)); + } + else if (value->getTypeString() == "color4") + { + Color4 v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(Color4)); + } + else if (value->getTypeString() == "vector2") + { + Vector2 v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(Vector2)); + } + else if (value->getTypeString() == "vector3") + { + Vector3 v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(Vector3)); + } + else if (value->getTypeString() == "vector4") + { + Vector4 v = value->asA(); + memcpy((void*)&data[offset], &v, sizeof(Vector4)); + } + else if (value->getTypeString() == "matrix33") + { + Matrix33 m = value->asA(); + float tmp[12] = {m[0][0], m[0][1], m[0][2], 0.0f, + m[1][0], m[1][1], m[1][2], 0.0f, + m[2][0], m[2][1], m[2][2], 0.0f}; + memcpy((void*)&data[offset], &tmp, sizeof(tmp)); + } + else if (value->getTypeString() == "matrix44") + { + Matrix44 m = value->asA(); + memcpy((void*)&data[offset], &m, sizeof(Matrix44)); + } + else if (value->getTypeString() == "string") + { + // Bound differently. Ignored here! + } + else + { + throw ExceptionRenderError( + "MSL input binding error.", + { "Unsupported data type when setting uniform value" } + ); + } + }; + + for (MTLArgument *arg in _psoReflection.vertexArguments) + { + if (arg.type == MTLArgumentTypeBuffer && arg.bufferDataType == MTLDataTypeStruct) + { + std::vector uniformBufferData(arg.bufferDataSize); + for (MTLStructMember* member in arg.bufferStructType.members) + { + if(!setCommonUniform(lightHandler, cam, member.name.UTF8String, uniformBufferData, member.offset)) + { + MaterialX::ValuePtr value = _uniformList[string(arg.name.UTF8String) + "." + member.name.UTF8String]->value; + if(value) + { + setValue(value, uniformBufferData, member.offset); + } + } + } + + if(arg.bufferStructType) + [renderCmdEncoder setVertexBytes:(void*)uniformBufferData.data() length:arg.bufferDataSize atIndex:arg.index]; + } + } + + for (MTLArgument *arg in _psoReflection.fragmentArguments) + { + if (arg.type == MTLArgumentTypeBuffer && arg.bufferDataType == MTLDataTypeStruct) + { + std::vector uniformBufferData(arg.bufferDataSize); + + for (MTLStructMember* member in arg.bufferStructType.members) + { + string uniformName = string(arg.name.UTF8String) + "." + member.name.UTF8String; + + if(!setCommonUniform(lightHandler, cam, member.name.UTF8String, uniformBufferData, member.offset)) + { + auto uniformInfo = _uniformList.find(uniformName); + if (uniformInfo != _uniformList.end()) + { + MaterialX::ValuePtr value = uniformInfo->second->value; + if(value) + { + setValue(value, uniformBufferData, member.offset); + } + } + else + { + + } + } + + if(MTLArrayType* arrayMember = member.arrayType) + { + for(int i = 0; i < arrayMember.arrayLength; ++i) + { + for (MTLStructMember* ArrayOfStructMember in arrayMember.elementStructType.members) + { + string uniformNameSubArray = uniformName + "[" + std::to_string(i) + "]." + ArrayOfStructMember.name.UTF8String; + + auto uniformInfo = _uniformList.find(uniformNameSubArray); + if (uniformInfo != _uniformList.end()) + { + MaterialX::ValuePtr value = uniformInfo->second->value; + if(value) + { + setValue(value, uniformBufferData, member.offset + i * arrayMember.stride + ArrayOfStructMember.offset); + } + } + } + } + } + } + + if(arg.bufferStructType) + [renderCmdEncoder setFragmentBytes:(void*)uniformBufferData.data() length:arg.bufferDataSize atIndex:arg.index]; + } + } +} + +void MslProgram::reset() +{ + if (_pso != nil) + { + [_pso release]; + } + _pso = nil; + + if(_psoReflection != nil) + { + [_psoReflection release]; + } + _psoReflection = nil; + + // Program deleted, so also clear cached input lists + clearInputLists(); +} + +MTLDataType MslProgram::mapTypeToMetalType(const TypeDesc* type) +{ + if (type == Type::INTEGER) + return MTLDataTypeInt; + else if (type == Type::BOOLEAN) + return MTLDataTypeBool; + else if (type == Type::FLOAT) + return MTLDataTypeFloat; + else if (type->isFloat2()) + return MTLDataTypeFloat2; + else if (type->isFloat3()) + return MTLDataTypeFloat3; + else if (type->isFloat4()) + return MTLDataTypeFloat4; + else if (type == Type::MATRIX33) + return MTLDataTypeFloat3x3; + else if (type == Type::MATRIX44) + return MTLDataTypeFloat4x4; + else if (type == Type::FILENAME) + { + // A "filename" is not indicative of type, so just return a 2d sampler. + return MTLDataTypeTexture; + } + else if (type == Type::BSDF || + type == Type::MATERIAL || + type == Type::DISPLACEMENTSHADER || + type == Type::EDF || + type == Type::VDF || + type == Type::SURFACESHADER || + type == Type::LIGHTSHADER || + type == Type::VOLUMESHADER) + return MTLDataTypeStruct; + + return MTLDataTypeNone; +} + +const MslProgram::InputMap& MslProgram::updateAttributesList() +{ + StringVec errors; + const string errorType("MSL attribute parsing error."); + + if (_pso == nil) + { + errors.push_back("Cannot parse for attributes without a valid program"); + throw ExceptionRenderError(errorType, errors); + } + + if (_shader) + { + const ShaderStage& vs = _shader->getStage(Stage::VERTEX); + + bool uniformTypeMismatchFound = false; + + const VariableBlock& vertexInputs = vs.getInputBlock(HW::VERTEX_INPUTS); + if (!vertexInputs.empty()) + { + for (size_t i = 0; i < vertexInputs.size(); ++i) + { + const ShaderPort* v = vertexInputs[i]; + + string variableName = v->getVariable(); + size_t dotPos = variableName.find('.'); + string variableMemberName = variableName.substr(dotPos + 1); + + auto inputIt = _attributeList.find(variableMemberName); + if (inputIt != _attributeList.end()) + { + Input* input = inputIt->second.get(); + input->value = v->getValue(); + if (input->resourceType == mapTypeToMetalType(v->getType())) + { + input->typeString = v->getType()->getName(); + } + else + { + errors.push_back( + "Vertex shader attribute type mismatch in block. Name: \"" + v->getVariable() + + "\". Type: \"" + v->getType()->getName() + + "\". Semantic: \"" + v->getSemantic() + + "\". Value: \"" + (v->getValue() ? v->getValue()->getValueString() : "") + + "\". resourceType: " + std::to_string(mapTypeToMetalType(v->getType())) + ); + uniformTypeMismatchFound = true; + } + } + } + } + + // Throw an error if any type mismatches were found + if (uniformTypeMismatchFound) + { + throw ExceptionRenderError(errorType, errors); + } + } + + return _attributeList; +} + +void MslProgram::findInputs(const string& variable, + const InputMap& variableList, + InputMap& foundList, + bool exactMatch) +{ + foundList.clear(); + + // Scan all attributes which match the attribute identifier completely or as a prefix + // + int ilocation = UNDEFINED_METAL_PROGRAM_LOCATION; + auto input = variableList.find(variable); + if (input != variableList.end()) + { + ilocation = input->second->location; + if (ilocation >= 0) + { + foundList[variable] = input->second; + } + } + else if (!exactMatch) + { + for (input = variableList.begin(); input != variableList.end(); ++input) + { + const string& name = input->first; + if (name.compare(0, variable.size(), variable) == 0) + { + ilocation = input->second->location; + if (ilocation >= 0) + { + foundList[input->first] = input->second; + } + } + } + } +} + +void MslProgram::printUniforms(std::ostream& outputStream) +{ + updateUniformsList(); + for (const auto& input : _uniformList) + { + unsigned int resourceType = input.second->resourceType; + int location = input.second->location; + int size = input.second->size; + string type = input.second->typeString; + string value = input.second->value ? input.second->value->getValueString() : EMPTY_STRING; + string unit = input.second->unit; + string colorspace = input.second->colorspace; + bool isConstant = input.second->isConstant; + outputStream << "Program Uniform: \"" << input.first + << "\". Location:" << location + << ". ResourceType: " << std::hex << resourceType + << ". Size: " << std::dec << size; + if (!type.empty()) + outputStream << ". TypeString: \"" << type << "\""; + if (!value.empty()) + { + outputStream << ". Value: " << value; + if (!unit.empty()) + outputStream << ". Unit: " << unit; + if (!colorspace.empty()) + outputStream << ". Colorspace: " << colorspace; + } + outputStream << ". Is constant: " << isConstant; + if (!input.second->path.empty()) + outputStream << ". Element Path: \"" << input.second->path << "\""; + outputStream << "." << std::endl; + } +} + + +void MslProgram::printAttributes(std::ostream& outputStream) +{ + updateAttributesList(); + for (const auto& input : _attributeList) + { + unsigned int resourceType = input.second->resourceType; + int location = input.second->location; + int size = input.second->size; + string type = input.second->typeString; + string value = input.second->value ? input.second->value->getValueString() : EMPTY_STRING; + outputStream << "Program Attribute: \"" << input.first + << "\". Location:" << location + << ". ResourceType: " << std::hex << resourceType + << ". Size: " << std::dec << size; + if (!type.empty()) + outputStream << ". TypeString: \"" << type << "\""; + if (!value.empty()) + outputStream << ". Value: " << value; + outputStream << "." << std::endl; + } +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderMsl/MslRenderer.h b/source/MaterialXRenderMsl/MslRenderer.h new file mode 100644 index 0000000000..74bb280a03 --- /dev/null +++ b/source/MaterialXRenderMsl/MslRenderer.h @@ -0,0 +1,160 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_MSLRENDERER_H +#define MATERIALX_MSLRENDERER_H + +/// @file +/// MSL code renderer + +#include + +#include +#include +#include + +#include + +#import + +MATERIALX_NAMESPACE_BEGIN + +using SimpleWindowPtr = std::shared_ptr; + +/// Shared pointer to a MslRenderer +using MslRendererPtr = std::shared_ptr; + +/// @class MslRenderer +/// Helper class for rendering generated MSL code to produce images. +/// +/// There are two main interfaces which can be used. One which takes in a HwShader and one which +/// allows for explicit setting of shader stage code. +/// +/// The main services provided are: +/// - Validation: All shader stages are compiled and atteched to a MSL shader program. +/// - Introspection: The compiled shader program is examined for uniforms and attributes. +/// - Binding: Uniforms and attributes which match the predefined variables generated the MSL code generator +/// will have values assigned to this. This includes matrices, attribute streams, and textures. +/// - Rendering: The program with bound inputs will be used to drawing geometry to an offscreen buffer. +/// An interface is provided to save this offscreen buffer to disk using an externally defined image handler. +/// +class MX_RENDERMSL_API MslRenderer : public ShaderRenderer +{ + public: + /// Create a MSL renderer instance + static MslRendererPtr create(unsigned int width = 512, unsigned int height = 512, Image::BaseType baseType = Image::BaseType::UINT8); + + /// Create a texture handler for Metal textures + ImageHandlerPtr createImageHandler(ImageLoaderPtr imageLoader) + { + return MetalTextureHandler::create(_device, imageLoader); + } + + /// Returns Metal Device used for rendering + id getMetalDevice() const; + + /// Destructor + virtual ~MslRenderer() { } + + /// @name Setup + /// @{ + + /// Internal initialization of stages and OpenGL constructs + /// required for program validation and rendering. + /// An exception is thrown on failure. + /// The exception will contain a list of initialization errors. + void initialize() override; + + /// @} + /// @name Rendering + /// @{ + + /// Create MSL program based on an input shader + /// @param shader Input HwShader + void createProgram(ShaderPtr shader) override; + + /// Create MSL program based on shader stage source code. + /// @param stages Map of name and source code for the shader stages. + void createProgram(const StageMap& stages) override; + + /// Validate inputs for the program + void validateInputs() override; + + /// Set the size of the rendered image + void setSize(unsigned int width, unsigned int height) override; + + /// Render the current program to an offscreen buffer. + void render() override; + + /// Render the current program in texture space to an off-screen buffer. + void renderTextureSpace(const Vector2& uvMin, const Vector2& uvMax); + + /// @} + /// @name Utilities + /// @{ + + /// Capture the current contents of the off-screen hardware buffer as an image. + ImagePtr captureImage(ImagePtr image = nullptr) override; + + /// Return the Metal frame buffer. + MetalFramebufferPtr getFramebuffer() const + { + return _framebuffer; + } + + /// Return the MSL program. + MslProgramPtr getProgram() + { + return _program; + } + + /// Set the screen background color. + void setScreenColor(const Color3& screenColor) + { + _screenColor = screenColor; + } + + /// Return the screen background color. + Color3 getScreenColor() const + { + return _screenColor; + } + + /// @} + + protected: + MslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType); + + virtual void updateViewInformation(); + virtual void updateWorldInformation(); + + void triggerProgrammaticCapture(); + void stopProgrammaticCapture(); + + void createFrameBuffer(bool encodeSrgb); + + private: + MslProgramPtr _program; + + id _device = nil; + id _cmdQueue = nil; + id _cmdBuffer = nil; + + MetalFramebufferPtr _framebuffer; + + bool _initialized; + + const Vector3 _eye; + const Vector3 _center; + const Vector3 _up; + float _objectScale; + + SimpleWindowPtr _window; + Color3 _screenColor; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRenderMsl/MslRenderer.mm b/source/MaterialXRenderMsl/MslRenderer.mm new file mode 100644 index 0000000000..f3aa7ba8b2 --- /dev/null +++ b/source/MaterialXRenderMsl/MslRenderer.mm @@ -0,0 +1,279 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include +#include + +#include + +#import + +MATERIALX_NAMESPACE_BEGIN + +const float PI = std::acos(-1.0f); + +// View information +const float FOV_PERSP = 45.0f; // degrees +const float NEAR_PLANE_PERSP = 0.05f; +const float FAR_PLANE_PERSP = 100.0f; + +// +// MslRenderer methods +// + +MslRendererPtr MslRenderer::create(unsigned int width, unsigned int height, Image::BaseType baseType) +{ + return MslRendererPtr(new MslRenderer(width, height, baseType)); +} + +id MslRenderer::getMetalDevice() const +{ + return _device; +} + +MslRenderer::MslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType) : + ShaderRenderer(width, height, baseType), + _initialized(false), + _eye(0.0f, 0.0f, 3.0f), + _center(0.0f, 0.0f, 0.0f), + _up(0.0f, 1.0f, 0.0f), + _objectScale(1.0f), + _screenColor(DEFAULT_SCREEN_COLOR_LIN_REC709) +{ + _program = MslProgram::create(); + + _geometryHandler = GeometryHandler::create(); + _geometryHandler->addLoader(TinyObjLoader::create()); + + _camera = Camera::create(); +} + +void MslRenderer::initialize() +{ + if (!_initialized) + { + // Create window + _window = SimpleWindow::create(); + + if (!_window->initialize("Renderer Window", _width, _height, nullptr)) + { + throw ExceptionRenderError("Failed to initialize renderer window"); + } + + _device = MTLCreateSystemDefaultDevice(); + _cmdQueue = [_device newCommandQueue]; + createFrameBuffer(true); + + _initialized = true; + } +} + +void MslRenderer::createProgram(ShaderPtr shader) +{ + _program = MslProgram::create(); + _program->setStages(shader); + _program->build(_device, _framebuffer); +} + +void MslRenderer::createProgram(const StageMap& stages) +{ + for (const auto& it : stages) + { + _program->addStage(it.first, it.second); + } + _program->build(_device, _framebuffer); +} + +void MslRenderer::renderTextureSpace(const Vector2& uvMin, const Vector2& uvMax) +{ + bool captureRenderTextureSpace = false; + if(captureRenderTextureSpace) + triggerProgrammaticCapture(); + + MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor new]; + _framebuffer->bind(desc); + + _cmdBuffer = [_cmdQueue commandBuffer]; + + id rendercmdEncoder = [_cmdBuffer renderCommandEncoderWithDescriptor:desc]; + _program->bind(rendercmdEncoder); + _program->prepareUsedResources(rendercmdEncoder, + _camera, + _geometryHandler, + _imageHandler, + _lightHandler); + + MeshPtr mesh = _geometryHandler->createQuadMesh(uvMin, uvMax, true); + _program->bindMesh(rendercmdEncoder, mesh); + MeshPartitionPtr part = mesh->getPartition(0); + _program->bindPartition(part); + MeshIndexBuffer& indexData = part->getIndices(); + [rendercmdEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle + indexCount:indexData.size() + indexType:MTLIndexTypeUInt32 + indexBuffer:_program->getIndexBuffer(part) + indexBufferOffset:0]; + + _framebuffer->unbind(); + [rendercmdEncoder endEncoding]; + + [_cmdBuffer commit]; + [_cmdBuffer waitUntilCompleted]; + + [desc release]; + + if(captureRenderTextureSpace) + stopProgrammaticCapture(); +} + +void MslRenderer::validateInputs() +{ + // Check that the generated uniforms and attributes are valid + _program->getUniformsList(); + _program->getAttributesList(); +} + +void MslRenderer::createFrameBuffer(bool encodeSrgb) +{ + _framebuffer = MetalFramebuffer::create(_device, + _width, _height, 4, + _baseType, + nil, encodeSrgb); +} + +void MslRenderer::setSize(unsigned int width, unsigned int height) +{ + if (_framebuffer) + { + _framebuffer->resize(width, height); + } + else + { + _width = width; + _height = height; + createFrameBuffer(true); + } + +} + +void MslRenderer::updateViewInformation() +{ + float fH = std::tan(FOV_PERSP / 360.0f * PI) * NEAR_PLANE_PERSP; + float fW = fH * 1.0f; + + _camera->setViewMatrix(Camera::createViewMatrix(_eye, _center, _up)); + _camera->setProjectionMatrix(Camera::createPerspectiveMatrixZP(-fW, fW, -fH, fH, NEAR_PLANE_PERSP, FAR_PLANE_PERSP)); +} + +void MslRenderer::updateWorldInformation() +{ + _camera->setWorldMatrix(Matrix44::createScale(Vector3(_objectScale))); +} + +void MslRenderer::triggerProgrammaticCapture() +{ + MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; + MTLCaptureDescriptor* captureDescriptor = [MTLCaptureDescriptor new]; + captureDescriptor.captureObject = _device; + + NSError* error = nil; + if(![captureManager startCaptureWithDescriptor:captureDescriptor error:&error]) + { + NSLog(@"Failed to start capture, error %@", error); + } +} + +void MslRenderer::stopProgrammaticCapture() +{ + MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; + [captureManager stopCapture]; +} + +void MslRenderer::render() +{ + bool captureFrame = false; + if(captureFrame) + triggerProgrammaticCapture(); + + _cmdBuffer = [_cmdQueue commandBuffer]; + MTLRenderPassDescriptor* renderpassDesc = [MTLRenderPassDescriptor new]; + + _framebuffer->bind(renderpassDesc); + [renderpassDesc.colorAttachments[0] setClearColor: + MTLClearColorMake(_screenColor[0], _screenColor[1], _screenColor[2], 1.0f)]; + + id renderCmdEncoder = [_cmdBuffer renderCommandEncoderWithDescriptor:renderpassDesc]; + + MTLDepthStencilDescriptor* depthStencilDesc = [MTLDepthStencilDescriptor new]; + depthStencilDesc.depthWriteEnabled = !(_program->isTransparent()); + depthStencilDesc.depthCompareFunction = MTLCompareFunctionLess; + + id depthStencilState = [_device newDepthStencilStateWithDescriptor:depthStencilDesc]; + [renderCmdEncoder setDepthStencilState:depthStencilState]; + + + [renderCmdEncoder setCullMode:MTLCullModeBack]; + + updateViewInformation(); + updateWorldInformation(); + + try + { + // Bind program and input parameters + if (_program) + { + // Bind the program to use + _program->bind(renderCmdEncoder); + _program->prepareUsedResources(renderCmdEncoder, _camera, _geometryHandler, _imageHandler, _lightHandler); + + // Draw all the partitions of all the meshes in the handler + for (const auto& mesh : _geometryHandler->getMeshes()) + { + for (size_t i = 0; i < mesh->getPartitionCount(); i++) + { + auto part = mesh->getPartition(i); + _program->bindPartition(part); + MeshIndexBuffer& indexData = part->getIndices(); + + if(_program->isTransparent()) + { + [renderCmdEncoder setCullMode:MTLCullModeFront]; + [renderCmdEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:(int)indexData.size() indexType:MTLIndexTypeUInt32 indexBuffer:_program->getIndexBuffer(part) indexBufferOffset:0]; + [renderCmdEncoder setCullMode:MTLCullModeBack]; + } + + [renderCmdEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:(int)indexData.size() indexType:MTLIndexTypeUInt32 indexBuffer:_program->getIndexBuffer(part) indexBufferOffset:0]; + } + } + } + } + catch (ExceptionRenderError& e) + { + _framebuffer->unbind(); + throw e; + } + + [renderCmdEncoder endEncoding]; + + _framebuffer->unbind(); + + [_cmdBuffer commit]; + [_cmdBuffer waitUntilCompleted]; + + [_cmdBuffer release]; + _cmdBuffer = nil; + + if(captureFrame) + stopProgrammaticCapture(); +} + +ImagePtr MslRenderer::captureImage(ImagePtr image) +{ + return _framebuffer->getColorImage(_cmdQueue, image); +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXRenderMsl/TextureBaker.h b/source/MaterialXRenderMsl/TextureBaker.h new file mode 100644 index 0000000000..e4f4f1e0de --- /dev/null +++ b/source/MaterialXRenderMsl/TextureBaker.h @@ -0,0 +1,51 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_TEXTUREBAKERMSL +#define MATERIALX_TEXTUREBAKERMSL + +/// @file +/// Texture baking functionality + +#include + +#include + +#include + +#include + +#include +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +/// A shared pointer to a TextureBakerMsl +using TextureBakerPtr = shared_ptr; + +/// A vector of baked documents with their associated names. +using BakedDocumentVec = std::vector>; + +/// @class TextureBakerMsl +/// A helper class for baking procedural material content to textures. +/// TODO: Add support for graphs containing geometric nodes such as position +/// and normal. +class MX_RENDERMSL_API TextureBakerMsl : public TextureBaker +{ + public: + static TextureBakerPtr create(unsigned int width = 1024, unsigned int height = 1024, Image::BaseType baseType = Image::BaseType::UINT8) + { + return TextureBakerPtr(new TextureBakerMsl(width, height, baseType)); + } + + protected: + TextureBakerMsl(unsigned int width, unsigned int height, Image::BaseType baseType); +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXRenderMsl/TextureBaker.mm b/source/MaterialXRenderMsl/TextureBaker.mm new file mode 100644 index 0000000000..682d2ffab5 --- /dev/null +++ b/source/MaterialXRenderMsl/TextureBaker.mm @@ -0,0 +1,23 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include + +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +TextureBakerMsl::TextureBakerMsl(unsigned int width, unsigned int height, Image::BaseType baseType) : + TextureBaker(width, height, baseType, false) +{ +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXTest/CMakeLists.txt b/source/MaterialXTest/CMakeLists.txt index f019c7b643..82696a53f3 100644 --- a/source/MaterialXTest/CMakeLists.txt +++ b/source/MaterialXTest/CMakeLists.txt @@ -43,7 +43,7 @@ target_link_libraries(MaterialXTest MaterialXFormat) add_subdirectory(MaterialXGenShader) target_link_libraries(MaterialXTest 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_subdirectory(MaterialXGenGlsl) target_link_libraries(MaterialXTest MaterialXGenGlsl) @@ -56,6 +56,10 @@ if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MD add_subdirectory(MaterialXGenMdl) target_link_libraries(MaterialXTest MaterialXGenMdl) endif() + if(MATERIALX_BUILD_GEN_MSL) + add_subdirectory(MaterialXGenMsl) + target_link_libraries(MaterialXTest MaterialXGenMsl) + endif() endif() if(MATERIALX_BUILD_RENDER) @@ -72,6 +76,10 @@ if(MATERIALX_BUILD_RENDER) add_subdirectory(MaterialXRenderOsl) target_link_libraries(MaterialXTest MaterialXRenderOsl) endif() + if(APPLE AND MATERIALX_BUILD_GEN_MSL) + add_subdirectory(MaterialXRenderMsl) + target_link_libraries(MaterialXTest MaterialXRenderMsl) + endif() if(MATERIALX_BUILD_OIIO AND OPENIMAGEIO_ROOT_DIR) add_custom_command(TARGET MaterialXTest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory diff --git a/source/MaterialXTest/MaterialXGenMsl/CMakeLists.txt b/source/MaterialXTest/MaterialXGenMsl/CMakeLists.txt new file mode 100644 index 0000000000..d5c17a2f6a --- /dev/null +++ b/source/MaterialXTest/MaterialXGenMsl/CMakeLists.txt @@ -0,0 +1,13 @@ +file(GLOB_RECURSE source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB_RECURSE headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") +file(GLOB_RECURSE objc_source "") +if(APPLE) + file(GLOB_RECURSE objc_source "${CMAKE_CURRENT_SOURCE_DIR}/*.mm") +endif() + +target_sources(MaterialXTest PUBLIC ${source} ${headers} ${objc_source}) + +add_tests("${source}") + +assign_source_group("Source Files" ${source}) +assign_source_group("Header Files" ${headers}) \ No newline at end of file diff --git a/source/MaterialXTest/MaterialXGenMsl/CompileMsl.h b/source/MaterialXTest/MaterialXGenMsl/CompileMsl.h new file mode 100644 index 0000000000..1b86dc720c --- /dev/null +++ b/source/MaterialXTest/MaterialXGenMsl/CompileMsl.h @@ -0,0 +1,15 @@ +// +// Copyright (c) 2023 Apple Inc. +// Licensed under the Apache License v2.0 +// + +#ifndef COMPILEMSL_H +#define COMPILEMSL_H + +#if __APPLE__ +void CompileMslShader(const char* pShaderFilePath, const char* pEntryFuncName); +#else +void CompileMslShader(const char*, const char* ) {} +#endif + +#endif // COMPILEMSL_H diff --git a/source/MaterialXTest/MaterialXGenMsl/CompileMsl.mm b/source/MaterialXTest/MaterialXGenMsl/CompileMsl.mm new file mode 100644 index 0000000000..2799b7c917 --- /dev/null +++ b/source/MaterialXTest/MaterialXGenMsl/CompileMsl.mm @@ -0,0 +1,46 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include "CompileMsl.h" + +#include +#include + +#include + +#import +id device = nil; + +void CompileMslShader(const char* pShaderFilePath, const char* pEntryFuncName) +{ + NSError* _Nullable error = nil; + if(device == nil) + device = MTLCreateSystemDefaultDevice(); + + NSString* filepath = [NSString stringWithUTF8String:pShaderFilePath]; + NSString* shadersource = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:&error]; + if(error != nil) + { + throw MaterialX::ExceptionShaderGenError("Cannot load file '" + std::string(pShaderFilePath) + "'."); + return; + } + + MTLCompileOptions* options = [MTLCompileOptions new]; +#ifdef MAC_OS_VERSION_11_0 + if (@available(macOS 11.0, ios 14.0, *)) + options.languageVersion = MTLLanguageVersion2_3; + else +#endif + options.languageVersion = MTLLanguageVersion2_0; + options.fastMathEnabled = true; + + id library = [device newLibraryWithSource:shadersource options:options error:&error]; + if(library == nil) + { + throw MaterialX::ExceptionShaderGenError("Failed to create library out of '" + std::string(pShaderFilePath) + "'." + + std::string(error ? [[error localizedDescription] UTF8String] : "")); + return; + } +} diff --git a/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp b/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp new file mode 100644 index 0000000000..8393acfca7 --- /dev/null +++ b/source/MaterialXTest/MaterialXGenMsl/GenMsl.cpp @@ -0,0 +1,155 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +namespace mx = MaterialX; + +TEST_CASE("GenShader: MSL Syntax Check", "[genmsl]") +{ + mx::SyntaxPtr syntax = mx::MslSyntax::create(); + + REQUIRE(syntax->getTypeName(mx::Type::FLOAT) == "float"); + REQUIRE(syntax->getTypeName(mx::Type::COLOR3) == "vec3"); + REQUIRE(syntax->getTypeName(mx::Type::VECTOR3) == "vec3"); + + REQUIRE(syntax->getTypeName(mx::Type::BSDF) == "BSDF"); + REQUIRE(syntax->getOutputTypeName(mx::Type::BSDF) == "thread BSDF&"); + + // Set fixed precision with one digit + mx::ScopedFloatFormatting format(mx::Value::FloatFormatFixed, 1); + + std::string value; + value = syntax->getDefaultValue(mx::Type::FLOAT); + REQUIRE(value == "0.0"); + value = syntax->getDefaultValue(mx::Type::COLOR3); + REQUIRE(value == "vec3(0.0)"); + value = syntax->getDefaultValue(mx::Type::COLOR3, true); + REQUIRE(value == "vec3(0.0)"); + value = syntax->getDefaultValue(mx::Type::COLOR4); + REQUIRE(value == "vec4(0.0)"); + value = syntax->getDefaultValue(mx::Type::COLOR4, true); + REQUIRE(value == "vec4(0.0)"); + value = syntax->getDefaultValue(mx::Type::FLOATARRAY, true); + REQUIRE(value.empty()); + value = syntax->getDefaultValue(mx::Type::INTEGERARRAY, true); + REQUIRE(value.empty()); + + mx::ValuePtr floatValue = mx::Value::createValue(42.0f); + value = syntax->getValue(mx::Type::FLOAT, *floatValue); + REQUIRE(value == "42.0"); + value = syntax->getValue(mx::Type::FLOAT, *floatValue, true); + REQUIRE(value == "42.0"); + + mx::ValuePtr color3Value = mx::Value::createValue(mx::Color3(1.0f, 2.0f, 3.0f)); + value = syntax->getValue(mx::Type::COLOR3, *color3Value); + REQUIRE(value == "vec3(1.0, 2.0, 3.0)"); + value = syntax->getValue(mx::Type::COLOR3, *color3Value, true); + REQUIRE(value == "vec3(1.0, 2.0, 3.0)"); + + mx::ValuePtr color4Value = mx::Value::createValue(mx::Color4(1.0f, 2.0f, 3.0f, 4.0f)); + value = syntax->getValue(mx::Type::COLOR4, *color4Value); + REQUIRE(value == "vec4(1.0, 2.0, 3.0, 4.0)"); + value = syntax->getValue(mx::Type::COLOR4, *color4Value, true); + REQUIRE(value == "vec4(1.0, 2.0, 3.0, 4.0)"); + + std::vector floatArray = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f }; + mx::ValuePtr floatArrayValue = mx::Value::createValue>(floatArray); + value = syntax->getValue(mx::Type::FLOATARRAY, *floatArrayValue); + REQUIRE(value == "{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7}"); + + std::vector intArray = { 1, 2, 3, 4, 5, 6, 7 }; + mx::ValuePtr intArrayValue = mx::Value::createValue>(intArray); + value = syntax->getValue(mx::Type::INTEGERARRAY, *intArrayValue); + REQUIRE(value == "{1, 2, 3, 4, 5, 6, 7}"); +} + +TEST_CASE("GenShader: MSL Implementation Check", "[genmsl]") +{ + mx::GenContext context(mx::MslShaderGenerator::create()); + + mx::StringSet generatorSkipNodeTypes; + mx::StringSet generatorSkipNodeDefs; + GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs, 48); +} + +TEST_CASE("GenShader: MSL Unique Names", "[genmsl]") +{ + mx::GenContext context(mx::MslShaderGenerator::create()); + + mx::FilePath searchPath = mx::FilePath::getCurrentPath() / mx::FilePath("libraries"); + context.registerSourceCodeSearchPath(searchPath); + + GenShaderUtil::testUniqueNames(context, mx::Stage::PIXEL); +} + +TEST_CASE("GenShader: MSL Bind Light Shaders", "[genmsl]") +{ + mx::DocumentPtr doc = mx::createDocument(); + + mx::FileSearchPath searchPath; + searchPath.append(mx::FilePath::getCurrentPath() / mx::FilePath("libraries")); + loadLibraries({ "targets", "stdlib", "pbrlib", "lights" }, searchPath, doc); + + mx::NodeDefPtr pointLightShader = doc->getNodeDef("ND_point_light"); + mx::NodeDefPtr spotLightShader = doc->getNodeDef("ND_spot_light"); + REQUIRE(pointLightShader != nullptr); + REQUIRE(spotLightShader != nullptr); + + mx::GenContext context(mx::MslShaderGenerator::create()); + context.registerSourceCodeSearchPath(mx::FilePath::getCurrentPath() / mx::FilePath("libraries")); + + mx::HwShaderGenerator::bindLightShader(*pointLightShader, 42, context); + REQUIRE_THROWS(mx::HwShaderGenerator::bindLightShader(*spotLightShader, 42, context)); + mx::HwShaderGenerator::unbindLightShader(42, context); + REQUIRE_NOTHROW(mx::HwShaderGenerator::bindLightShader(*spotLightShader, 42, context)); + REQUIRE_NOTHROW(mx::HwShaderGenerator::bindLightShader(*pointLightShader, 66, context)); + mx::HwShaderGenerator::unbindLightShaders(context); + REQUIRE_NOTHROW(mx::HwShaderGenerator::bindLightShader(*spotLightShader, 66, context)); +} + +static void generateMslCode() +{ + const mx::FilePath testRootPath = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/TestSuite"); + const mx::FilePath testRootPath2 = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/Examples/StandardSurface"); + const mx::FilePath testRootPath3 = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/Examples/UsdPreviewSurface"); + mx::FilePathVec testRootPaths; + testRootPaths.push_back(testRootPath); + testRootPaths.push_back(testRootPath2); + testRootPaths.push_back(testRootPath3); + const mx::FilePath libSearchPath = mx::FilePath::getCurrentPath() / mx::FilePath("libraries"); + const mx::FileSearchPath srcSearchPath(libSearchPath.asString()); + bool writeShadersToDisk = false; + + const mx::GenOptions genOptions; + mx::FilePath optionsFilePath = testRootPath / mx::FilePath("_options.mtlx"); + + const mx::FilePath logPath("genmsl_msl23_layout_generate_test.txt"); + + MslShaderGeneratorTester tester(mx::MslShaderGenerator::create(), testRootPaths, libSearchPath, srcSearchPath, logPath, writeShadersToDisk); + + // Set binding context to handle resource binding layouts + tester.addUserData(mx::HW::USER_DATA_BINDING_CONTEXT, mx::MslResourceBindingContext::create()); + + tester.validate(genOptions, optionsFilePath); +} + +TEST_CASE("GenShader: MSL Shader with Layout Generation", "[genmsl]") +{ + // Generate with standard MSL 2.3 + generateMslCode(); +} diff --git a/source/MaterialXTest/MaterialXGenMsl/GenMsl.h b/source/MaterialXTest/MaterialXGenMsl/GenMsl.h new file mode 100644 index 0000000000..114ab854d3 --- /dev/null +++ b/source/MaterialXTest/MaterialXGenMsl/GenMsl.h @@ -0,0 +1,78 @@ +// +// Copyright (c) 2023 Apple Inc. +// Licensed under the Apache License v2.0 +// + +#ifndef GENGLSL_H +#define GENGLSL_H + +#include + +#include +#include + +#include "CompileMsl.h" + +#include + +namespace mx = MaterialX; + +class MslShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester +{ + public: + using ParentClass = GenShaderUtil::ShaderGeneratorTester; + + MslShaderGeneratorTester(mx::ShaderGeneratorPtr shaderGenerator, const mx::FilePathVec& testRootPaths, + const mx::FilePath& libSearchPath, const mx::FileSearchPath& srcSearchPath, + const mx::FilePath& logFilePath, bool writeShadersToDisk) : + GenShaderUtil::ShaderGeneratorTester(shaderGenerator, testRootPaths, libSearchPath, srcSearchPath, logFilePath, writeShadersToDisk) + {} + + void setTestStages() override + { + _testStages.push_back(mx::Stage::VERTEX); + _testStages.push_back(mx::Stage::PIXEL); + } + + // Ignore trying to create shader code for displacementshaders + void addSkipNodeDefs() override + { + _skipNodeDefs.insert("ND_displacement_float"); + _skipNodeDefs.insert("ND_displacement_vector3"); + ParentClass::addSkipNodeDefs(); + } + + void setupDependentLibraries() override + { + ParentClass::setupDependentLibraries(); + + mx::FilePath lightDir = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/TestSuite/lights"); + loadLibrary(lightDir / mx::FilePath("light_compound_test.mtlx"), _dependLib); + loadLibrary(lightDir / mx::FilePath("light_rig_test_1.mtlx"), _dependLib); + } + + void compileSource(const std::vector& sourceCodePaths) override + { + int i = 0; + for(const mx::FilePath& sourceCodePath : sourceCodePaths) + { + assert(i == 0 || i == 1); + CompileMslShader(sourceCodePath.asString().c_str(), i == 0 ? "VertexMain" : "FragmentMain"); + ++i; + } + } + + protected: + void getImplementationWhiteList(mx::StringSet& whiteList) override + { + whiteList = + { + "ambientocclusion", "arrayappend", "backfacing", "screen", "curveadjust", "displacementshader", + "volumeshader", "IM_constant_", "IM_dot_", "IM_geompropvalue_boolean", "IM_geompropvalue_string", + "IM_light_genmsl", "IM_point_light_genmsl", "IM_spot_light_genmsl", "IM_directional_light_genmsl", + "IM_angle", "surfacematerial", "volumematerial", "ND_surfacematerial", "ND_volumematerial", "ND_backface_util", "IM_backface_util_genmsl" + }; + } +}; + +#endif // GENGLSL_H diff --git a/source/MaterialXTest/MaterialXGenShader/GenShader.cpp b/source/MaterialXTest/MaterialXGenShader/GenShader.cpp index 2f33383675..293398cbc1 100644 --- a/source/MaterialXTest/MaterialXGenShader/GenShader.cpp +++ b/source/MaterialXTest/MaterialXGenShader/GenShader.cpp @@ -24,6 +24,9 @@ #ifdef MATERIALX_BUILD_GEN_MDL #include #endif +#ifdef MATERIALX_BUILD_GEN_MSL +#include +#endif #include #include @@ -256,6 +259,13 @@ TEST_CASE("GenShader: Deterministic Generation", "[genshader]") testDeterministicGeneration(libraries, context); } #endif +#ifdef MATERIALX_BUILD_GEN_MSL + { + mx::GenContext context(mx::MslShaderGenerator::create()); + context.registerSourceCodeSearchPath(searchPath); + testDeterministicGeneration(libraries, context); + } +#endif } void checkPixelDependencies(mx::DocumentPtr libraries, mx::GenContext& context) diff --git a/source/MaterialXTest/MaterialXRender/RenderUtil.cpp b/source/MaterialXTest/MaterialXRender/RenderUtil.cpp index f7fc2c40be..6f4c7d7c9a 100644 --- a/source/MaterialXTest/MaterialXRender/RenderUtil.cpp +++ b/source/MaterialXTest/MaterialXRender/RenderUtil.cpp @@ -427,4 +427,222 @@ bool ShaderRenderTester::validate(const mx::FilePath optionsFilePath) return true; } +void ShaderRenderTester::addAdditionalTestStreams(mx::MeshPtr mesh) +{ + size_t vertexCount = mesh->getVertexCount(); + if (vertexCount < 1) + { + return; + } + + const std::string TEXCOORD_STREAM0_NAME("i_" + mx::MeshStream::TEXCOORD_ATTRIBUTE + "_0"); + mx::MeshStreamPtr texCoordStream1 = mesh->getStream(TEXCOORD_STREAM0_NAME); + mx::MeshFloatBuffer uv = texCoordStream1->getData(); + + const std::string TEXCOORD_STREAM1_NAME("i_" + mx::MeshStream::TEXCOORD_ATTRIBUTE + "_1"); + mx::MeshFloatBuffer* texCoordData2 = nullptr; + if (!mesh->getStream(TEXCOORD_STREAM1_NAME)) + { + mx::MeshStreamPtr texCoordStream2 = mx::MeshStream::create(TEXCOORD_STREAM1_NAME, mx::MeshStream::TEXCOORD_ATTRIBUTE, 1); + texCoordStream2->setStride(2); + texCoordData2 = &(texCoordStream2->getData()); + texCoordData2->resize(vertexCount * 2); + mesh->addStream(texCoordStream2); + } + + const std::string COLOR_STREAM0_NAME("i_" + mx::MeshStream::COLOR_ATTRIBUTE + "_0"); + mx::MeshFloatBuffer* colorData1 = nullptr; + if (!mesh->getStream(COLOR_STREAM0_NAME)) + { + mx::MeshStreamPtr colorStream1 = mx::MeshStream::create(COLOR_STREAM0_NAME, mx::MeshStream::COLOR_ATTRIBUTE, 0); + colorData1 = &(colorStream1->getData()); + colorStream1->setStride(4); + colorData1->resize(vertexCount * 4); + mesh->addStream(colorStream1); + } + + const std::string COLOR_STREAM1_NAME("i_" + mx::MeshStream::COLOR_ATTRIBUTE + "_1"); + mx::MeshFloatBuffer* colorData2 = nullptr; + if (!mesh->getStream(COLOR_STREAM1_NAME)) + { + mx::MeshStreamPtr colorStream2 = mx::MeshStream::create(COLOR_STREAM1_NAME, mx::MeshStream::COLOR_ATTRIBUTE, 1); + colorData2 = &(colorStream2->getData()); + colorStream2->setStride(4); + colorData2->resize(vertexCount * 4); + mesh->addStream(colorStream2); + } + + const std::string GEOM_INT_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_integer"); + int32_t* geomIntData = nullptr; + if (!mesh->getStream(GEOM_INT_STREAM_NAME)) + { + mx::MeshStreamPtr geomIntStream = mx::MeshStream::create(GEOM_INT_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 0); + geomIntStream->setStride(1); + geomIntStream->getData().resize(vertexCount); + mesh->addStream(geomIntStream); + // Float and int32 have same size. + geomIntData = reinterpret_cast(geomIntStream->getData().data()); + } + + const std::string GEOM_FLOAT_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_float"); + mx::MeshFloatBuffer* geomFloatData = nullptr; + if (!mesh->getStream(GEOM_FLOAT_STREAM_NAME)) + { + mx::MeshStreamPtr geomFloatStream = mx::MeshStream::create(GEOM_FLOAT_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomFloatData = &(geomFloatStream->getData()); + geomFloatStream->setStride(1); + geomFloatData->resize(vertexCount); + mesh->addStream(geomFloatStream); + } + + const std::string GEOM_VECTOR2_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_vector2"); + mx::MeshFloatBuffer* geomVector2Data = nullptr; + if (!mesh->getStream(GEOM_VECTOR2_STREAM_NAME)) + { + mx::MeshStreamPtr geomVector2Stream = mx::MeshStream::create(GEOM_VECTOR2_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomVector2Data = &(geomVector2Stream->getData()); + geomVector2Stream->setStride(2); + geomVector2Data->resize(vertexCount * 2); + mesh->addStream(geomVector2Stream); + } + + const std::string GEOM_VECTOR3_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_vector3"); + mx::MeshFloatBuffer* geomVector3Data = nullptr; + if (!mesh->getStream(GEOM_VECTOR3_STREAM_NAME)) + { + mx::MeshStreamPtr geomVector3Stream = mx::MeshStream::create(GEOM_VECTOR3_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomVector3Data = &(geomVector3Stream->getData()); + geomVector3Stream->setStride(3); + geomVector3Data->resize(vertexCount * 3); + mesh->addStream(geomVector3Stream); + } + + const std::string GEOM_VECTOR4_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_vector4"); + mx::MeshFloatBuffer* geomVector4Data = nullptr; + if (!mesh->getStream(GEOM_VECTOR4_STREAM_NAME)) + { + mx::MeshStreamPtr geomVector4Stream = mx::MeshStream::create(GEOM_VECTOR4_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomVector4Data = &(geomVector4Stream->getData()); + geomVector4Stream->setStride(4); + geomVector4Data->resize(vertexCount * 4); + mesh->addStream(geomVector4Stream); + } + + const std::string GEOM_COLOR2_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_color2"); + mx::MeshFloatBuffer* geomColor2Data = nullptr; + if (!mesh->getStream(GEOM_COLOR2_STREAM_NAME)) + { + mx::MeshStreamPtr geomColor2Stream = mx::MeshStream::create(GEOM_COLOR2_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomColor2Data = &(geomColor2Stream->getData()); + geomColor2Stream->setStride(2); + geomColor2Data->resize(vertexCount * 2); + mesh->addStream(geomColor2Stream); + } + + const std::string GEOM_COLOR3_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_color3"); + mx::MeshFloatBuffer* geomColor3Data = nullptr; + if (!mesh->getStream(GEOM_COLOR3_STREAM_NAME)) + { + mx::MeshStreamPtr geomColor3Stream = mx::MeshStream::create(GEOM_COLOR3_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomColor3Data = &(geomColor3Stream->getData()); + geomColor3Stream->setStride(3); + geomColor3Data->resize(vertexCount * 3); + mesh->addStream(geomColor3Stream); + } + + const std::string GEOM_COLOR4_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_color4"); + mx::MeshFloatBuffer* geomColor4Data = nullptr; + if (!mesh->getStream(GEOM_COLOR4_STREAM_NAME)) + { + mx::MeshStreamPtr geomColor4Stream = mx::MeshStream::create(GEOM_COLOR4_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); + geomColor4Data = &(geomColor4Stream->getData()); + geomColor4Stream->setStride(4); + geomColor4Data->resize(vertexCount * 4); + mesh->addStream(geomColor4Stream); + } + + auto sineData = [](float uv, float freq){ + const float PI = std::acos(-1.0f); + float angle = uv * 2 * PI * freq; + return std::sin(angle) / 2.0f + 1.0f; + }; + if (!uv.empty()) + { + for (size_t i = 0; i < vertexCount; i++) + { + const size_t i2 = 2 * i; + const size_t i21 = i2 + 1; + const size_t i3 = 3 * i; + const size_t i4 = 4 * i; + + // Fake second set of texture coordinates + if (texCoordData2) + { + (*texCoordData2)[i2] = uv[i21]; + (*texCoordData2)[i21] = uv[i2]; + } + if (colorData1) + { + // Fake some colors + (*colorData1)[i4] = uv[i2]; + (*colorData1)[i4 + 1] = uv[i21]; + (*colorData1)[i4 + 2] = 1.0f; + (*colorData1)[i4 + 3] = 1.0f; + } + if (colorData2) + { + (*colorData2)[i4] = 1.0f; + (*colorData2)[i4 + 1] = uv[i2]; + (*colorData2)[i4 + 2] = uv[i21]; + (*colorData2)[i4 + 3] = 1.0f; + } + if (geomIntData) + { + geomIntData[i] = static_cast(uv[i21] * 5); + } + if (geomFloatData) + { + (*geomFloatData)[i] = sineData(uv[i21], 12.0f); + } + if (geomVector2Data) + { + (*geomVector2Data)[i2] = sineData(uv[i21], 6.0f); + (*geomVector2Data)[i21] = 0.0f; + } + if (geomVector3Data) + { + (*geomVector3Data)[i3] = 0.0f; + (*geomVector3Data)[i3 + 1] = sineData(uv[i21], 8.0f); + (*geomVector3Data)[i3 + 2] = 0.0f; + } + if (geomVector4Data) + { + (*geomVector4Data)[i4] = 0.0f; + (*geomVector4Data)[i4 + 1] = 0.0f; + (*geomVector4Data)[i4 + 2] = sineData(uv[i21], 10.0f); + (*geomVector4Data)[i4 + 3] = 1.0f; + } + + if (geomColor2Data) + { + (*geomColor2Data)[i2] = sineData(uv[i2], 10.0f); + (*geomColor2Data)[i21] = 0.0f; + } + if (geomColor3Data) + { + (*geomColor3Data)[i3] = 0.0f; + (*geomColor3Data)[i3 + 1] = sineData(uv[i2], 8.0f); + (*geomColor3Data)[i3 + 2] = 0.0f; + } + if (geomColor4Data) + { + (*geomColor4Data)[i4] = 0.0f; + (*geomColor4Data)[i4 + 1] = 0.0f; + (*geomColor4Data)[i4 + 2] = sineData(uv[i2], 6.0f); + (*geomColor4Data)[i4 + 3] = 1.0f; + } + } + } +} + } // namespace RenderUtil diff --git a/source/MaterialXTest/MaterialXRender/RenderUtil.h b/source/MaterialXTest/MaterialXRender/RenderUtil.h index e2df5aab1f..9f892569de 100644 --- a/source/MaterialXTest/MaterialXRender/RenderUtil.h +++ b/source/MaterialXTest/MaterialXRender/RenderUtil.h @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -170,6 +171,9 @@ class ShaderRenderTester virtual bool canBake() const { return false; } virtual void runBake(mx::DocumentPtr /*doc*/, const mx::FileSearchPath& /*codeSearchPath*/, const mx::FilePath& /*outputFilename*/, const GenShaderUtil::TestSuiteOptions::BakeSetting& /*bakeOptions*/, std::ostream& /*log*/) {}; + + // If these streams don't exist add them for testing purposes + void addAdditionalTestStreams(mx::MeshPtr mesh); // Generator to use mx::ShaderGeneratorPtr _shaderGenerator; diff --git a/source/MaterialXTest/MaterialXRenderGlsl/RenderGlsl.cpp b/source/MaterialXTest/MaterialXRenderGlsl/RenderGlsl.cpp index e31f8a6b5e..48c76e65a3 100644 --- a/source/MaterialXTest/MaterialXRenderGlsl/RenderGlsl.cpp +++ b/source/MaterialXTest/MaterialXRenderGlsl/RenderGlsl.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include +#include #include #include @@ -121,7 +124,7 @@ void GlslShaderRenderTester::createRenderer(std::ostream& log) // Set image handler on renderer mx::StbImageLoaderPtr stbLoader = mx::StbImageLoader::create(); - mx::ImageHandlerPtr imageHandler = mx::GLTextureHandler::create(stbLoader); + mx::ImageHandlerPtr imageHandler = _renderer->createImageHandler(stbLoader); #if defined(MATERIALX_BUILD_OIIO) mx::OiioImageLoaderPtr oiioLoader = mx::OiioImageLoader::create(); imageHandler->addLoader(oiioLoader); @@ -152,226 +155,6 @@ bool GlslShaderRenderTester::saveImage(const mx::FilePath& filePath, mx::ConstIm return _renderer->getImageHandler()->saveImage(filePath, image, verticalFlip); } -// If these streams don't exist add them for testing purposes -// -void addAdditionalTestStreams(mx::MeshPtr mesh) -{ - size_t vertexCount = mesh->getVertexCount(); - if (vertexCount < 1) - { - return; - } - - const std::string TEXCOORD_STREAM0_NAME("i_" + mx::MeshStream::TEXCOORD_ATTRIBUTE + "_0"); - mx::MeshStreamPtr texCoordStream1 = mesh->getStream(TEXCOORD_STREAM0_NAME); - mx::MeshFloatBuffer uv = texCoordStream1->getData(); - - const std::string TEXCOORD_STREAM1_NAME("i_" + mx::MeshStream::TEXCOORD_ATTRIBUTE + "_1"); - mx::MeshFloatBuffer* texCoordData2 = nullptr; - if (!mesh->getStream(TEXCOORD_STREAM1_NAME)) - { - mx::MeshStreamPtr texCoordStream2 = mx::MeshStream::create(TEXCOORD_STREAM1_NAME, mx::MeshStream::TEXCOORD_ATTRIBUTE, 1); - texCoordStream2->setStride(2); - texCoordData2 = &(texCoordStream2->getData()); - texCoordData2->resize(vertexCount * 2); - mesh->addStream(texCoordStream2); - } - - const std::string COLOR_STREAM0_NAME("i_" + mx::MeshStream::COLOR_ATTRIBUTE + "_0"); - mx::MeshFloatBuffer* colorData1 = nullptr; - if (!mesh->getStream(COLOR_STREAM0_NAME)) - { - mx::MeshStreamPtr colorStream1 = mx::MeshStream::create(COLOR_STREAM0_NAME, mx::MeshStream::COLOR_ATTRIBUTE, 0); - colorData1 = &(colorStream1->getData()); - colorStream1->setStride(4); - colorData1->resize(vertexCount * 4); - mesh->addStream(colorStream1); - } - - const std::string COLOR_STREAM1_NAME("i_" + mx::MeshStream::COLOR_ATTRIBUTE + "_1"); - mx::MeshFloatBuffer* colorData2 = nullptr; - if (!mesh->getStream(COLOR_STREAM1_NAME)) - { - mx::MeshStreamPtr colorStream2 = mx::MeshStream::create(COLOR_STREAM1_NAME, mx::MeshStream::COLOR_ATTRIBUTE, 1); - colorData2 = &(colorStream2->getData()); - colorStream2->setStride(4); - colorData2->resize(vertexCount * 4); - mesh->addStream(colorStream2); - } - - const std::string GEOM_INT_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_integer"); - int32_t* geomIntData = nullptr; - if (!mesh->getStream(GEOM_INT_STREAM_NAME)) - { - mx::MeshStreamPtr geomIntStream = mx::MeshStream::create(GEOM_INT_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 0); - geomIntStream->setStride(1); - geomIntStream->getData().resize(vertexCount); - mesh->addStream(geomIntStream); - // Float and int32 have same size. - geomIntData = reinterpret_cast(geomIntStream->getData().data()); - } - - const std::string GEOM_FLOAT_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_float"); - mx::MeshFloatBuffer* geomFloatData = nullptr; - if (!mesh->getStream(GEOM_FLOAT_STREAM_NAME)) - { - mx::MeshStreamPtr geomFloatStream = mx::MeshStream::create(GEOM_FLOAT_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomFloatData = &(geomFloatStream->getData()); - geomFloatStream->setStride(1); - geomFloatData->resize(vertexCount); - mesh->addStream(geomFloatStream); - } - - const std::string GEOM_VECTOR2_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_vector2"); - mx::MeshFloatBuffer* geomVector2Data = nullptr; - if (!mesh->getStream(GEOM_VECTOR2_STREAM_NAME)) - { - mx::MeshStreamPtr geomVector2Stream = mx::MeshStream::create(GEOM_VECTOR2_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomVector2Data = &(geomVector2Stream->getData()); - geomVector2Stream->setStride(2); - geomVector2Data->resize(vertexCount * 2); - mesh->addStream(geomVector2Stream); - } - - const std::string GEOM_VECTOR3_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_vector3"); - mx::MeshFloatBuffer* geomVector3Data = nullptr; - if (!mesh->getStream(GEOM_VECTOR3_STREAM_NAME)) - { - mx::MeshStreamPtr geomVector3Stream = mx::MeshStream::create(GEOM_VECTOR3_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomVector3Data = &(geomVector3Stream->getData()); - geomVector3Stream->setStride(3); - geomVector3Data->resize(vertexCount * 3); - mesh->addStream(geomVector3Stream); - } - - const std::string GEOM_VECTOR4_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_vector4"); - mx::MeshFloatBuffer* geomVector4Data = nullptr; - if (!mesh->getStream(GEOM_VECTOR4_STREAM_NAME)) - { - mx::MeshStreamPtr geomVector4Stream = mx::MeshStream::create(GEOM_VECTOR4_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomVector4Data = &(geomVector4Stream->getData()); - geomVector4Stream->setStride(4); - geomVector4Data->resize(vertexCount * 4); - mesh->addStream(geomVector4Stream); - } - - const std::string GEOM_COLOR2_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_color2"); - mx::MeshFloatBuffer* geomColor2Data = nullptr; - if (!mesh->getStream(GEOM_COLOR2_STREAM_NAME)) - { - mx::MeshStreamPtr geomColor2Stream = mx::MeshStream::create(GEOM_COLOR2_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomColor2Data = &(geomColor2Stream->getData()); - geomColor2Stream->setStride(2); - geomColor2Data->resize(vertexCount * 2); - mesh->addStream(geomColor2Stream); - } - - const std::string GEOM_COLOR3_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_color3"); - mx::MeshFloatBuffer* geomColor3Data = nullptr; - if (!mesh->getStream(GEOM_COLOR3_STREAM_NAME)) - { - mx::MeshStreamPtr geomColor3Stream = mx::MeshStream::create(GEOM_COLOR3_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomColor3Data = &(geomColor3Stream->getData()); - geomColor3Stream->setStride(3); - geomColor3Data->resize(vertexCount * 3); - mesh->addStream(geomColor3Stream); - } - - const std::string GEOM_COLOR4_STREAM_NAME("i_" + mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE + "_geompropvalue_color4"); - mx::MeshFloatBuffer* geomColor4Data = nullptr; - if (!mesh->getStream(GEOM_COLOR4_STREAM_NAME)) - { - mx::MeshStreamPtr geomColor4Stream = mx::MeshStream::create(GEOM_COLOR4_STREAM_NAME, mx::MeshStream::GEOMETRY_PROPERTY_ATTRIBUTE, 1); - geomColor4Data = &(geomColor4Stream->getData()); - geomColor4Stream->setStride(4); - geomColor4Data->resize(vertexCount * 4); - mesh->addStream(geomColor4Stream); - } - - auto sineData = [](float uv, float freq){ - const float PI = std::acos(-1.0f); - float angle = uv * 2 * PI * freq; - return std::sin(angle) / 2.0f + 1.0f; - }; - if (!uv.empty()) - { - for (size_t i = 0; i < vertexCount; i++) - { - const size_t i2 = 2 * i; - const size_t i21 = i2 + 1; - const size_t i3 = 3 * i; - const size_t i4 = 4 * i; - - // Fake second set of texture coordinates - if (texCoordData2) - { - (*texCoordData2)[i2] = uv[i21]; - (*texCoordData2)[i21] = uv[i2]; - } - if (colorData1) - { - // Fake some colors - (*colorData1)[i4] = uv[i2]; - (*colorData1)[i4 + 1] = uv[i21]; - (*colorData1)[i4 + 2] = 1.0f; - (*colorData1)[i4 + 3] = 1.0f; - } - if (colorData2) - { - (*colorData2)[i4] = 1.0f; - (*colorData2)[i4 + 1] = uv[i2]; - (*colorData2)[i4 + 2] = uv[i21]; - (*colorData2)[i4 + 3] = 1.0f; - } - if (geomIntData) - { - geomIntData[i] = static_cast(uv[i21] * 5); - } - if (geomFloatData) - { - (*geomFloatData)[i] = sineData(uv[i21], 12.0f); - } - if (geomVector2Data) - { - (*geomVector2Data)[i2] = sineData(uv[i21], 6.0f); - (*geomVector2Data)[i21] = 0.0f; - } - if (geomVector3Data) - { - (*geomVector3Data)[i3] = 0.0f; - (*geomVector3Data)[i3 + 1] = sineData(uv[i21], 8.0f); - (*geomVector3Data)[i3 + 2] = 0.0f; - } - if (geomVector4Data) - { - (*geomVector4Data)[i4] = 0.0f; - (*geomVector4Data)[i4 + 1] = 0.0f; - (*geomVector4Data)[i4 + 2] = sineData(uv[i21], 10.0f); - (*geomVector4Data)[i4 + 3] = 1.0f; - } - - if (geomColor2Data) - { - (*geomColor2Data)[i2] = sineData(uv[i2], 10.0f); - (*geomColor2Data)[i21] = 0.0f; - } - if (geomColor3Data) - { - (*geomColor3Data)[i3] = 0.0f; - (*geomColor3Data)[i3 + 1] = sineData(uv[i2], 8.0f); - (*geomColor3Data)[i3 + 2] = 0.0f; - } - if (geomColor4Data) - { - (*geomColor4Data)[i4] = 0.0f; - (*geomColor4Data)[i4 + 1] = 0.0f; - (*geomColor4Data)[i4 + 2] = sineData(uv[i2], 6.0f); - (*geomColor4Data)[i4 + 3] = 1.0f; - } - } - } -} - bool GlslShaderRenderTester::runRenderer(const std::string& shaderName, mx::TypedElementPtr element, mx::GenContext& context, @@ -642,7 +425,7 @@ void GlslShaderRenderTester::runBake(mx::DocumentPtr doc, const mx::FileSearchPa const unsigned bakeHeight = std::max(bakeOptions.resolution, maxImageSize.second); mx::Image::BaseType baseType = bakeOptions.hdr ? mx::Image::BaseType::FLOAT : mx::Image::BaseType::UINT8; - mx::TextureBakerPtr baker = mx::TextureBaker::create(bakeWidth, bakeHeight, baseType); + mx::TextureBakerPtr baker = mx::TextureBakerGlsl::create(bakeWidth, bakeHeight, baseType); baker->setupUnitSystem(doc); baker->setImageHandler(_renderer->getImageHandler()); baker->setOptimizeConstants(true); diff --git a/source/MaterialXTest/MaterialXRenderMsl/CMakeLists.txt b/source/MaterialXTest/MaterialXRenderMsl/CMakeLists.txt new file mode 100644 index 0000000000..ef7de33f43 --- /dev/null +++ b/source/MaterialXTest/MaterialXRenderMsl/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB_RECURSE source "${CMAKE_CURRENT_SOURCE_DIR}/*.mm") +file(GLOB_RECURSE headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") + +target_sources(MaterialXTest PUBLIC ${source} ${headers}) + +add_tests("${source}") + +assign_source_group("Source Files" ${source}) +assign_source_group("Header Files" ${headers}) diff --git a/source/MaterialXTest/MaterialXRenderMsl/RenderMsl.mm b/source/MaterialXTest/MaterialXRenderMsl/RenderMsl.mm new file mode 100644 index 0000000000..7a76ae08dd --- /dev/null +++ b/source/MaterialXTest/MaterialXRenderMsl/RenderMsl.mm @@ -0,0 +1,471 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifdef __APPLE__ + +#include +#include + +#include + +#include +#include +#if defined(MATERIALX_BUILD_OIIO) +#include +#endif + +#include +#include + +#include + +#include + +namespace mx = MaterialX; + +// +// Render validation tester for the Metal shading language +// +class MslShaderRenderTester : public RenderUtil::ShaderRenderTester +{ + public: + explicit MslShaderRenderTester(mx::ShaderGeneratorPtr shaderGenerator) : + RenderUtil::ShaderRenderTester(shaderGenerator) + { + } + + protected: + void loadAdditionalLibraries(mx::DocumentPtr document, + GenShaderUtil::TestSuiteOptions& options) override; + + void registerLights(mx::DocumentPtr document, const GenShaderUtil::TestSuiteOptions &options, + mx::GenContext& context) override; + + void createRenderer(std::ostream& log) override; + + bool runRenderer(const std::string& shaderName, + mx::TypedElementPtr element, + mx::GenContext& context, + mx::DocumentPtr doc, + std::ostream& log, + const GenShaderUtil::TestSuiteOptions& testOptions, + RenderUtil::RenderProfileTimes& profileTimes, + const mx::FileSearchPath& imageSearchPath, + const std::string& outputPath = ".", + mx::ImageVec* imageVec = nullptr) override; + + bool saveImage(const mx::FilePath& filePath, mx::ConstImagePtr image, bool verticalFlip) const override; + + bool canBake() const override + { + return true; + } + + void runBake(mx::DocumentPtr doc, const mx::FileSearchPath& imageSearchPath, const mx::FilePath& outputFilename, + const GenShaderUtil::TestSuiteOptions::BakeSetting& bakeOptions, std::ostream& log) override; + + mx::MslRendererPtr _renderer; + mx::LightHandlerPtr _lightHandler; + id _device; +}; + +// In addition to standard texture and shader definition libraries, additional lighting files +// are loaded in. If no files are specifed in the input options, a sample +// compound light type and a set of lights in a "light rig" are loaded in to a given +// document. +void MslShaderRenderTester::loadAdditionalLibraries(mx::DocumentPtr document, + GenShaderUtil::TestSuiteOptions& options) +{ + mx::FilePath lightDir = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/TestSuite/lights"); + for (const auto& lightFile : options.lightFiles) + { + loadLibrary(lightDir / mx::FilePath(lightFile), document); + } +} + +// Create a light handler and populate it based on lights found in a given document +void MslShaderRenderTester::registerLights(mx::DocumentPtr document, + const GenShaderUtil::TestSuiteOptions &options, + mx::GenContext& context) +{ + _lightHandler = mx::LightHandler::create(); + + // Scan for lights + std::vector lights; + _lightHandler->findLights(document, lights); + _lightHandler->registerLights(document, lights, context); + + // Set the list of lights on the with the generator + _lightHandler->setLightSources(lights); + + // Load environment lights. + mx::ImagePtr envRadiance = _renderer->getImageHandler()->acquireImage(options.radianceIBLPath); + mx::ImagePtr envIrradiance = _renderer->getImageHandler()->acquireImage(options.irradianceIBLPath); + REQUIRE(envRadiance); + REQUIRE(envIrradiance); + + // Apply light settings for render tests. + _lightHandler->setEnvRadianceMap(envRadiance); + _lightHandler->setEnvIrradianceMap(envIrradiance); + _lightHandler->setEnvSampleCount(options.enableReferenceQuality ? 4096 : 1024); +} + +// +// Create a renderer with the apporpraite image, geometry and light handlers. +// The light handler on the renderer is cleared on initialization to indicate no lighting +// is required. During code generation, if the element to validate requires lighting then +// the handler _lightHandler will be used. +// +void MslShaderRenderTester::createRenderer(std::ostream& log) +{ + bool initialized = false; + try + { + _renderer = mx::MslRenderer::create(); + _renderer->initialize(); + + _device = _renderer->getMetalDevice(); + + // Set image handler on renderer + mx::StbImageLoaderPtr stbLoader = mx::StbImageLoader::create(); + mx::ImageHandlerPtr imageHandler = + _renderer->createImageHandler(stbLoader); +#if defined(MATERIALX_BUILD_OIIO) + mx::OiioImageLoaderPtr oiioLoader = mx::OiioImageLoader::create(); + imageHandler->addLoader(oiioLoader); +#endif + _renderer->setImageHandler(imageHandler); + + // Set light handler. + _renderer->setLightHandler(nullptr); + + initialized = true; + } + catch (mx::ExceptionRenderError& e) + { + for (const auto& error : e.errorLog()) + { + log << e.what() << " " << error << std::endl; + } + } + catch (mx::Exception& e) + { + log << e.what() << std::endl; + } + REQUIRE(initialized); +} + +bool MslShaderRenderTester::saveImage(const mx::FilePath& filePath, mx::ConstImagePtr image, bool verticalFlip) const +{ + return _renderer->getImageHandler()->saveImage(filePath, image, verticalFlip); +} + +bool MslShaderRenderTester::runRenderer(const std::string& shaderName, + mx::TypedElementPtr element, + mx::GenContext& context, + mx::DocumentPtr doc, + std::ostream& log, + const GenShaderUtil::TestSuiteOptions& testOptions, + RenderUtil::RenderProfileTimes& profileTimes, + const mx::FileSearchPath& imageSearchPath, + const std::string& outputPath, + mx::ImageVec* imageVec) +{ + std::cout << "Validating MSL rendering for: " << doc->getSourceUri() << std::endl; + + mx::ScopedTimer totalMSLTime(&profileTimes.languageTimes.totalTime); + + const mx::ShaderGenerator& shadergen = context.getShaderGenerator(); + + // Perform validation if requested + if (testOptions.validateElementToRender) + { + std::string message; + if (!element->validate(&message)) + { + log << "Element is invalid: " << message << std::endl; + return false; + } + } + + std::vector optionsList; + getGenerationOptions(testOptions, context.getOptions(), optionsList); + + if (element && doc) + { + log << "------------ Run MSL validation with element: " << element->getNamePath() << "-------------------" << std::endl; + + for (auto options : optionsList) + { + profileTimes.elementsTested++; + + mx::FilePath outputFilePath = outputPath; + // Use separate directory for reduced output + if (options.shaderInterfaceType == mx::SHADER_INTERFACE_REDUCED) + { + outputFilePath = outputFilePath / mx::FilePath("reduced"); + } + + // Note: mkdir will fail if the directory already exists which is ok. + { + mx::ScopedTimer ioDir(&profileTimes.languageTimes.ioTime); + outputFilePath.createDirectory(); + } + + std::string shaderPath = mx::FilePath(outputFilePath) / mx::FilePath(shaderName); + mx::ShaderPtr shader; + try + { + mx::ScopedTimer transpTimer(&profileTimes.languageTimes.transparencyTime); + options.hwTransparency = mx::isTransparentSurface(element, shadergen.getTarget()); + transpTimer.endTimer(); + + mx::ScopedTimer generationTimer(&profileTimes.languageTimes.generationTime); + mx::GenOptions& contextOptions = context.getOptions(); + contextOptions = options; + contextOptions.targetColorSpaceOverride = "lin_rec709"; + shader = shadergen.generate(shaderName, element, context); + generationTimer.endTimer(); + } + catch (mx::Exception& e) + { + log << ">> " << e.what() << "\n"; + shader = nullptr; + } + + CHECK(shader != nullptr); + if (shader == nullptr) + { + log << ">> Failed to generate shader\n"; + return false; + } + const std::string& vertexSourceCode = shader->getSourceCode(mx::Stage::VERTEX); + const std::string& pixelSourceCode = shader->getSourceCode(mx::Stage::PIXEL); + CHECK(vertexSourceCode.length() > 0); + CHECK(pixelSourceCode.length() > 0); + + if (testOptions.dumpGeneratedCode) + { + mx::ScopedTimer dumpTimer(&profileTimes.languageTimes.ioTime); + std::ofstream file; + file.open(shaderPath + "_vs.metal"); + file << vertexSourceCode; + file.close(); + file.open(shaderPath + "_ps.metal"); + file << pixelSourceCode; + file.close(); + } + + if (!testOptions.compileCode) + { + return false; + } + + // Validate + bool validated = false; + try + { + // Set geometry + mx::GeometryHandlerPtr geomHandler = _renderer->getGeometryHandler(); + mx::FilePath geomPath; + if (!testOptions.renderGeometry.isEmpty()) + { + if (!testOptions.renderGeometry.isAbsolute()) + { + geomPath = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Geometry") / testOptions.renderGeometry; + } + else + { + geomPath = testOptions.renderGeometry; + } + } + else + { + geomPath = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Geometry/sphere.obj"); + } + + if (!geomHandler->hasGeometry(geomPath)) + { + // For test sphere and plane geometry perform a V-flip of texture coordinates. + const std::string baseName = geomPath.getBaseName(); + bool texcoordVerticalFlip = baseName == "sphere.obj" || baseName == "plane.obj"; + geomHandler->clearGeometry(); + geomHandler->loadGeometry(geomPath, texcoordVerticalFlip); + for (mx::MeshPtr mesh : geomHandler->getMeshes()) + { + addAdditionalTestStreams(mesh); + } + } + + bool isShader = mx::elementRequiresShading(element); + _renderer->setLightHandler(isShader ? _lightHandler : nullptr); + { + mx::ScopedTimer compileTimer(&profileTimes.languageTimes.compileTime); + _renderer->createProgram(shader); + _renderer->validateInputs(); + } + + if (testOptions.dumpUniformsAndAttributes) + { + MaterialX::MslProgramPtr program = _renderer->getProgram(); + mx::ScopedTimer printTimer(&profileTimes.languageTimes.ioTime); + log << "* Uniform:" << std::endl; + program->printUniforms(log); + log << "* Attributes:" << std::endl; + program->printAttributes(log); + + log << "* Uniform UI Properties:" << std::endl; + const std::string& target = shadergen.getTarget(); + const MaterialX::MslProgram::InputMap& uniforms = program->getUniformsList(); + for (const auto& uniform : uniforms) + { + const std::string& path = uniform.second->path; + if (path.empty()) + { + continue; + } + + mx::UIProperties uiProperties; + mx::ElementPtr pathElement = doc->getDescendant(path); + mx::InputPtr input = pathElement ? pathElement->asA() : nullptr; + if (getUIProperties(input, target, uiProperties) > 0) + { + log << "Program Uniform: " << uniform.first << ". Path: " << path; + if (!uiProperties.uiName.empty()) + log << ". UI Name: \"" << uiProperties.uiName << "\""; + if (!uiProperties.uiFolder.empty()) + log << ". UI Folder: \"" << uiProperties.uiFolder << "\""; + if (!uiProperties.enumeration.empty()) + { + log << ". Enumeration: {"; + for (size_t i = 0; i < uiProperties.enumeration.size(); i++) + log << uiProperties.enumeration[i] << " "; + log << "}"; + } + if (!uiProperties.enumerationValues.empty()) + { + log << ". Enum Values: {"; + for (size_t i = 0; i < uiProperties.enumerationValues.size(); i++) + log << uiProperties.enumerationValues[i]->getValueString() << "; "; + log << "}"; + } + if (uiProperties.uiMin) + log << ". UI Min: " << uiProperties.uiMin->getValueString(); + if (uiProperties.uiMax) + log << ". UI Max: " << uiProperties.uiMax->getValueString(); + if (uiProperties.uiSoftMin) + log << ". UI Soft Min: " << uiProperties.uiSoftMin->getValueString(); + if (uiProperties.uiSoftMax) + log << ". UI Soft Max: " << uiProperties.uiSoftMax->getValueString(); + if (uiProperties.uiStep) + log << ". UI Step: " << uiProperties.uiStep->getValueString(); + log << std::endl; + } + } + } + + if (testOptions.renderImages) + { + { + mx::ScopedTimer renderTimer(&profileTimes.languageTimes.renderTime); + _renderer->getImageHandler()->setSearchPath(imageSearchPath); + _renderer->setSize(static_cast(testOptions.renderSize[0]), static_cast(testOptions.renderSize[1])); + _renderer->render(); + } + + if (testOptions.saveImages) + { + mx::ScopedTimer ioTimer(&profileTimes.languageTimes.imageSaveTime); + std::string fileName = shaderPath + "_msl.png"; + mx::ImagePtr image = _renderer->captureImage(); + if (image) + { + _renderer->getImageHandler()->saveImage(fileName, image, true); + if (imageVec) + { + imageVec->push_back(image); + } + } + } + } + + validated = true; + } + catch (mx::ExceptionRenderError& e) + { + // Always dump shader stages on error + std::ofstream file; + file.open(shaderPath + "_vs.metal"); + file << shader->getSourceCode(mx::Stage::VERTEX); + file.close(); + file.open(shaderPath + "_ps.metal"); + file << shader->getSourceCode(mx::Stage::PIXEL); + file.close(); + + for (const auto& error : e.errorLog()) + { + log << e.what() << " " << error << std::endl; + } + log << ">> Refer to shader code in dump files: " << shaderPath << "_ps.metal and _vs.metal files" << std::endl; + WARN(std::string(e.what()) + " in " + shaderPath); + } + catch (mx::Exception& e) + { + log << e.what() << std::endl; + WARN(std::string(e.what()) + " in " + shaderPath); + } + CHECK(validated); + } + } + return true; +} + +void MslShaderRenderTester::runBake(mx::DocumentPtr doc, const mx::FileSearchPath& imageSearchPath, const mx::FilePath& outputFileName, + const GenShaderUtil::TestSuiteOptions::BakeSetting& bakeOptions, std::ostream& log) +{ + mx::ImageVec imageVec = _renderer->getImageHandler()->getReferencedImages(doc); + auto maxImageSize = mx::getMaxDimensions(imageVec); + const unsigned bakeWidth = std::max(bakeOptions.resolution, maxImageSize.first); + const unsigned bakeHeight = std::max(bakeOptions.resolution, maxImageSize.second); + + mx::Image::BaseType baseType = bakeOptions.hdr ? mx::Image::BaseType::FLOAT : mx::Image::BaseType::UINT8; + mx::TextureBakerPtr baker = mx::TextureBakerMsl::create(bakeWidth, bakeHeight, baseType); + baker->setupUnitSystem(doc); + baker->setImageHandler(_renderer->getImageHandler()); + baker->setOptimizeConstants(true); + baker->setHashImageNames(true); + baker->setTextureSpaceMin(bakeOptions.uvmin); + baker->setTextureSpaceMax(bakeOptions.uvmax); + + try + { + baker->setOutputStream(&log); + baker->bakeAllMaterials(doc, imageSearchPath, outputFileName); + } + catch (mx::Exception& e) + { + const mx::FilePath& sourceUri = doc->getSourceUri(); + log << sourceUri.asString() + " failed baking process: " + e.what() << std::endl; + } +} + +TEST_CASE("Render: MSL TestSuite", "[rendermsl]") +{ + MslShaderRenderTester renderTester(mx::MslShaderGenerator::create()); + + const mx::FilePath testRootPath = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/TestSuite"); + const mx::FilePath testRootPath2 = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/Examples/StandardSurface"); + const mx::FilePath testRootPath3 = mx::FilePath::getCurrentPath() / mx::FilePath("resources/Materials/Examples/UsdPreviewSurface"); + mx::FilePathVec testRootPaths; + testRootPaths.push_back(testRootPath); + testRootPaths.push_back(testRootPath2); + testRootPaths.push_back(testRootPath3); + + mx::FilePath optionsFilePath = testRootPath / mx::FilePath("_options.mtlx"); + + renderTester.validate(optionsFilePath); +} + +#endif diff --git a/source/MaterialXTest/README.md b/source/MaterialXTest/README.md index c6efbaaca5..8cd732f2cf 100644 --- a/source/MaterialXTest/README.md +++ b/source/MaterialXTest/README.md @@ -33,6 +33,7 @@ Refer to the [test suite documentation](../../resources/Materials/TestSuite/READ - GenGlsl.cpp : GLSL shader generation tests which are run when the test tag `[genglsl]` is specified. - GenOsl.cpp : OSL shader generation tests which are run when the test tag `[genosl]` is specified. - GenMdl.cpp : MDL shader generation tests which are run when the test tag `[genmdl]` is specified. +- GenMsl.cpp : MSL shader generation tests which are run when the test tage `[genmsl]` is specified. Per-language tests will scan MaterialX files in the test suite for input materials. @@ -47,6 +48,7 @@ Depending on which tests are executed log files are produced at the location tha - Render.cpp : Core render tests which are run when the test tag `[rendercore]` is specified. - RenderGlsl.cpp : GLSL render tests which are run when the test tag `[renderglsl]` is specified. - RenderOsl.cpp : OSL render tests which are run when the test tag `[renderosl]` is specified. +- RenderMsl.mm: MSL render tests which are run when the test tage `[rendermsl]` is specified. Per language tests will scan MaterialX files in the test suite for input materials. @@ -67,6 +69,9 @@ When rendering tests are enabled through the `MATERIALX_TEST_RENDER` option, the - `MATERIALX_MDL_RENDER_EXECUTABLE`: Full path to the binary for render testing. - Optionally, `MATERIALX_MDL_RENDER_ARGUMENTS` can be set to provide command line arguments for non-interactive rendering. - MDL versions 1.6 and later are supported. +- `MSL`: + - Metal Shading Language (MSL) 2.0 and later are supported. + - Minimum tested operating system version is macOS Catalina 10.15.7 #### Test Outputs diff --git a/source/MaterialXView/CMakeLists.txt b/source/MaterialXView/CMakeLists.txt index e5f9eff45a..1d9a329e46 100644 --- a/source/MaterialXView/CMakeLists.txt +++ b/source/MaterialXView/CMakeLists.txt @@ -2,6 +2,30 @@ set(CMAKE_CXX_STANDARD 17) option(MATERIALX_NANOGUI_EXTERNAL "Build aginst an external install of NanoGUI (NANOGUI_ROOT may also need to be set)" OFF) +set(MATERIALXVIEW_RENDER_BACKEND_DEFINITIONS "") + +set(NANOGUI_PREFERRED_BACKEND OpenGL) +if(APPLE) + option(USE_OPENGL_BACKEND_ON_APPLE_PLATFORM "Use OpenGL Backend on Apple Platform" OFF) + + # Disables MaterialXView Metal Rendering on MacOS version below 10.14 + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_HOST_SYSTEM_VERSION VERSION_LESS 19) + set(USE_OPENGL_BACKEND_ON_APPLE_PLATFORM ON) + endif() + + if(USE_OPENGL_BACKEND_ON_APPLE_PLATFORM) + set(NANOGUI_PREFERRED_BACKEND OpenGL) + set(MATERIALXVIEW_RENDER_BACKEND_DEFINITIONS "-DMATERIALXVIEW_OPENGL_BACKEND=1") + else() + set(NANOGUI_PREFERRED_BACKEND Metal) + set(MATERIALXVIEW_RENDER_BACKEND_DEFINITIONS "-DMATERIALXVIEW_METAL_BACKEND=1") + endif() +endif() + +if("${MATERIALXVIEW_RENDER_BACKEND_DEFINITIONS}" STREQUAL "") + set(MATERIALXVIEW_RENDER_BACKEND_DEFINITIONS "-DMATERIALXVIEW_OPENGL_BACKEND=1") +endif() + if (MATERIALX_NANOGUI_EXTERNAL) find_path(NANOGUI_INCLUDE_DIRS NAMES @@ -28,11 +52,15 @@ else() "git submodule update --init --recursive") endif() - set(NANOGUI_BACKEND OpenGL CACHE STRING " " FORCE) + set(NANOGUI_BACKEND ${NANOGUI_PREFERRED_BACKEND} CACHE STRING " " FORCE) set(NANOGUI_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE) set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE) set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE) set(NANOGUI_INSTALL OFF CACHE BOOL " " FORCE) + + if(APPLE) + set(NANOGUI_NATIVE_FLAGS CACHE STRING "" FORCE) + endif() # Locally disable additional warnings for NanoGUI and its dependencies set(PREV_CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) @@ -65,17 +93,34 @@ else() set(CMAKE_CXX_COMPILER_ID PREV_COMPILER_ID) endif() -file(GLOB materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") -file(GLOB materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*") +file(GLOB materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*") add_definitions(${NANOGUI_EXTRA_DEFS}) - -add_executable(MaterialXView ${materialx_source} ${materialx_headers}) +add_definitions(${MATERIALXVIEW_RENDER_BACKEND_DEFINITIONS}) set(MATERIALX_LIBRARIES MaterialXFormat MaterialXGenGlsl + MaterialXRender MaterialXRenderGlsl) + +if("${NANOGUI_PREFERRED_BACKEND}" STREQUAL "Metal") + set(MATERIALX_LIBRARIES + MaterialXFormat + MaterialXRender + MaterialXGenMsl + MaterialXRenderMsl) + + file(GLOB materialx_source + "${CMAKE_CURRENT_SOURCE_DIR}/Viewer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ViewerMSL.mm" + "${CMAKE_CURRENT_SOURCE_DIR}/RenderPipeline.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RenderPipelineMetal.mm" + "${CMAKE_CURRENT_SOURCE_DIR}/Editor.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MetalState.mm" + "${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp") +endif() if (MATERIALX_BUILD_GEN_OSL) LIST(APPEND MATERIALX_LIBRARIES MaterialXGenOsl) @@ -84,6 +129,8 @@ if (MATERIALX_BUILD_GEN_MDL) LIST(APPEND MATERIALX_LIBRARIES MaterialXGenMdl) endif() +add_executable(MaterialXView ${materialx_source} ${materialx_headers}) + target_link_libraries( MaterialXView ${MATERIALX_LIBRARIES} diff --git a/source/MaterialXView/Editor.cpp b/source/MaterialXView/Editor.cpp index b6a55d9be9..a4c6d3f675 100644 --- a/source/MaterialXView/Editor.cpp +++ b/source/MaterialXView/Editor.cpp @@ -199,7 +199,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st comboBox->set_font_size(15); comboBox->set_callback([path, viewer, enumeration, enumValues](int index) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { if (index >= 0 && static_cast(index) < enumValues.size()) @@ -226,7 +226,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st intVar->set_spinnable(editable); intVar->set_callback([intVar, path, viewer](int /*unclamped*/) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { // https://github.com/wjakob/nanogui/issues/205 @@ -256,7 +256,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st threeColumns->set_layout(_gridLayout3); ng::FloatBox* floatBox = createFloatWidget(threeColumns, label, value->asA(), &ui, [viewer, path](float value) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { material->modifyUniform(path, mx::Value::createValue(value)); @@ -279,7 +279,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st boolVar->set_font_size(15); boolVar->set_callback([path, viewer](bool v) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { material->modifyUniform(path, mx::Value::createValue((float) v)); @@ -319,7 +319,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st comboBox->set_font_size(15); comboBox->set_callback([path, enumValues, viewer](int index) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { if (index < (int) enumValues.size()) @@ -340,7 +340,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st colorVar->set_font_size(15); colorVar->set_final_callback([path, viewer](const ng::Color &c) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector3 v(c.r(), c.g(), c.b()); @@ -364,7 +364,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st colorVar->set_font_size(15); colorVar->set_final_callback([path, viewer](const ng::Color &c) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector4 v(c.r(), c.g(), c.b(), c.w()); @@ -390,7 +390,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v2->set_font_size(15); v1->set_callback([v2, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector2 v(f, v2->value()); @@ -401,7 +401,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v1->set_editable(editable); v2->set_callback([v1, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector2 v(v1->value(), f); @@ -434,7 +434,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v1->set_callback([v2, v3, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector3 v(f, v2->value(), v3->value()); @@ -445,7 +445,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v1->set_editable(editable); v2->set_callback([v1, v3, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector3 v(v1->value(), f, v3->value()); @@ -456,7 +456,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v2->set_editable(editable); v3->set_callback([v1, v2, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector3 v(v1->value(), v2->value(), f); @@ -493,7 +493,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v1->set_callback([v2, v3, v4, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector4 v(f, v2->value(), v3->value(), v4->value()); @@ -503,7 +503,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v1->set_spinnable(editable); v2->set_callback([v1, v3, v4, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector4 v(v1->value(), f, v3->value(), v4->value()); @@ -514,7 +514,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v2->set_editable(editable); v3->set_callback([v1, v2, v4, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector4 v(v1->value(), v2->value(), f, v4->value()); @@ -525,7 +525,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st v3->set_editable(editable); v4->set_callback([v1, v2, v3, path, viewer](float f) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); if (material) { mx::Vector4 v(v1->value(), v2->value(), v3->value(), f); @@ -553,7 +553,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st buttonVar->set_font_size(15); buttonVar->set_callback([buttonVar, path, viewer]() { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); mx::ShaderPort* uniform = material ? material->findUniform(path) : nullptr; if (uniform) { @@ -588,7 +588,7 @@ void PropertyEditor::addItemToForm(const mx::UIPropertyItem& item, const std::st stringVar->set_font_size(15); stringVar->set_callback([path, viewer](const std::string &v) { - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); mx::ShaderPort* uniform = material ? material->findUniform(path) : nullptr; if (uniform) { @@ -605,15 +605,17 @@ void PropertyEditor::updateContents(Viewer* viewer) { create(*viewer); - mx::GlslMaterialPtr material = viewer->getSelectedMaterial(); + mx::MaterialPtr material = viewer->getSelectedMaterial(); mx::TypedElementPtr elem = material ? material->getElement() : nullptr; if (!material || !elem) { return; } +#ifndef MATERIALXVIEW_METAL_BACKEND // Bind and validate the shader material->bindShader(); +#endif // Shading model display mx::NodePtr node = elem->asA(); diff --git a/source/MaterialXView/RenderPipeline.cpp b/source/MaterialXView/RenderPipeline.cpp new file mode 100644 index 0000000000..1f1e405ed7 --- /dev/null +++ b/source/MaterialXView/RenderPipeline.cpp @@ -0,0 +1,23 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include + +RenderPipeline::RenderPipeline(Viewer* viewer) +{ + _viewer = viewer; +} + +void RenderPipeline::renderScreenSpaceQuad(mx::MaterialPtr material) +{ + if (!_quadMesh) + _quadMesh = mx::GeometryHandler::createQuadMesh(); + + material->bindMesh(_quadMesh); + material->drawPartition(_quadMesh->getPartition(0)); +} diff --git a/source/MaterialXView/RenderPipeline.h b/source/MaterialXView/RenderPipeline.h new file mode 100644 index 0000000000..426caef199 --- /dev/null +++ b/source/MaterialXView/RenderPipeline.h @@ -0,0 +1,69 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef RENDER_PIPELINE_H +#define RENDER_PIPELINE_H + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +MATERIALX_NAMESPACE_BEGIN +#ifdef MATERIALXVIEW_METAL_BACKEND +using TextureBakerPtr = shared_ptr; +#else +using TextureBakerPtr = shared_ptr; +#endif +MATERIALX_NAMESPACE_END + +#include + +namespace mx = MaterialX; + +class Viewer; +using RenderPipelinePtr = std::shared_ptr; + +class RenderPipeline +{ + public: + RenderPipeline() = delete; + RenderPipeline(Viewer* viewer); + virtual ~RenderPipeline() { } + + // Initialize the viewer for rendering. + virtual void initialize(void* device, void* command_queue) = 0; + + virtual mx::ImageHandlerPtr createImageHandler() = 0; + virtual mx::MaterialPtr createMaterial() = 0; + virtual void bakeTextures() = 0; + + virtual void updateAlbedoTable(int tableSize) = 0; + virtual std::shared_ptr createTextureBaker(unsigned int width, + unsigned int height, + mx::Image::BaseType baseType) = 0; + + virtual void renderFrame(void* color_texture, int shadowMapSize, const char* dirLightNodeCat) = 0; + + virtual void initFramebuffer(int width, int height, + void* color_texture) = 0; + virtual void resizeFramebuffer(int width, int height, + void* color_texture) = 0; + + void renderScreenSpaceQuad(mx::MaterialPtr material); + + virtual mx::ImagePtr getShadowMap(int shadowMapSize) = 0; + + Viewer* _viewer; + mx::MeshPtr _quadMesh; +}; +#endif // RENDER_PIPELINE_H diff --git a/source/MaterialXView/RenderPipelineGL.cpp b/source/MaterialXView/RenderPipelineGL.cpp new file mode 100644 index 0000000000..52e4210bbe --- /dev/null +++ b/source/MaterialXView/RenderPipelineGL.cpp @@ -0,0 +1,428 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace +{ +const float PI = std::acos(-1.0f); +} + +GLRenderPipeline::GLRenderPipeline(Viewer* viewerPtr) : + RenderPipeline(viewerPtr) +{ +} + +void GLRenderPipeline::initialize(void*, void*) +{ +} + +mx::ImageHandlerPtr GLRenderPipeline::createImageHandler() +{ + return mx::GLTextureHandler::create(mx::StbImageLoader::create()); +} + +mx::MaterialPtr GLRenderPipeline::createMaterial() +{ + return mx::GlslMaterial::create(); +} + +std::shared_ptr GLRenderPipeline::createTextureBaker(unsigned int width, + unsigned int height, + mx::Image::BaseType baseType) +{ + return std::static_pointer_cast(mx::TextureBakerGlsl::create(width, height, baseType)); +} + +void GLRenderPipeline::initFramebuffer(int, int, void*) +{ + +} + +void GLRenderPipeline::resizeFramebuffer(int, int, void*) +{ + +} + +void GLRenderPipeline::updateAlbedoTable(int tableSize) +{ + auto& genContext = _viewer->_genContext; + auto& stdLib = _viewer->_stdLib; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + + if (lightHandler->getAlbedoTable()) + { + return; + } + + // Create framebuffer. + mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(tableSize, tableSize, 3, mx::Image::BaseType::FLOAT); + framebuffer->bind(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + // Create shader. + mx::ShaderPtr hwShader = mx::createAlbedoTableShader(genContext, stdLib, "__ALBEDO_TABLE_SHADER__"); + mx::GlslMaterialPtr material = mx::GlslMaterial::create(); + try + { + material->generateShader(hwShader); + } + catch (std::exception& e) + { + new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to generate albedo table shader", e.what()); + return; + } + + // Render albedo table. + material->bindShader(); + if (material->getProgram()->hasUniform(mx::HW::ALBEDO_TABLE_SIZE)) + { + std::static_pointer_cast(material)->getProgram() + ->bindUniform(mx::HW::ALBEDO_TABLE_SIZE, mx::Value::createValue(tableSize)); + } + renderScreenSpaceQuad(material); + + // Store albedo table image. + imageHandler->releaseRenderResources(lightHandler->getAlbedoTable()); + lightHandler->setAlbedoTable(framebuffer->getColorImage()); + if (_viewer->_saveGeneratedLights) + { + imageHandler->saveImage("AlbedoTable.exr", lightHandler->getAlbedoTable()); + } + + // Restore state for scene rendering. + glViewport(0, 0, _viewer->m_fbsize[0], _viewer->m_fbsize[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDrawBuffer(GL_BACK); +} + +mx::ImagePtr GLRenderPipeline::getShadowMap(int shadowMapSize) +{ + auto& genContext = _viewer->_genContext; + auto& imageHandler = _viewer->_imageHandler; + auto& shadowCamera = _viewer->_shadowCamera; + auto& stdLib = _viewer->_stdLib; + auto& geometryHandler = _viewer->_geometryHandler; + + if (!_viewer->_shadowMap) + { + // Generate shaders for shadow rendering. + if (!_viewer->_shadowMaterial) + { + try + { + mx::ShaderPtr hwShader = mx::createDepthShader(genContext, stdLib, "__SHADOW_SHADER__"); + _viewer->_shadowMaterial = mx::GlslMaterial::create(); + _viewer->_shadowMaterial->generateShader(hwShader); + } + catch (std::exception& e) + { + std::cerr << "Failed to generate shadow shader: " << e.what() << std::endl; + _viewer->_shadowMaterial = nullptr; + } + } + if (!_viewer->_shadowBlurMaterial) + { + try + { + mx::ShaderPtr hwShader = mx::createBlurShader(genContext, stdLib, "__SHADOW_BLUR_SHADER__", "gaussian", 1.0f); + _viewer->_shadowBlurMaterial = mx::GlslMaterial::create(); + _viewer->_shadowBlurMaterial->generateShader(hwShader); + } + catch (std::exception& e) + { + std::cerr << "Failed to generate shadow blur shader: " << e.what() << std::endl; + _viewer->_shadowBlurMaterial = nullptr; + } + } + + if (_viewer->_shadowMaterial && _viewer->_shadowBlurMaterial) + { + // Create framebuffer. + mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(shadowMapSize, shadowMapSize, 2, mx::Image::BaseType::FLOAT); + framebuffer->bind(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + // Render shadow geometry. + _viewer->_shadowMaterial->bindShader(); + for (auto mesh : geometryHandler->getMeshes()) + { + _viewer->_shadowMaterial->bindMesh(mesh); + _viewer->_shadowMaterial->bindViewInformation(shadowCamera); + for (size_t i = 0; i < mesh->getPartitionCount(); i++) + { + mx::MeshPartitionPtr geom = mesh->getPartition(i); + _viewer->_shadowMaterial->drawPartition(geom); + } + } + _viewer->_shadowMap = framebuffer->getColorImage(); + + // Apply Gaussian blurring. + mx::ImageSamplingProperties blurSamplingProperties; + blurSamplingProperties.uaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; + blurSamplingProperties.vaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; + blurSamplingProperties.filterType = mx::ImageSamplingProperties::FilterType::CLOSEST; + for (unsigned int i = 0; i < _viewer->_shadowSoftness; i++) + { + framebuffer->bind(); + _viewer->_shadowBlurMaterial->bindShader(); + if (imageHandler->bindImage(_viewer->_shadowMap, blurSamplingProperties)) + { + mx::GLTextureHandlerPtr textureHandler = std::static_pointer_cast(imageHandler); + int textureLocation = textureHandler->getBoundTextureLocation(_viewer->_shadowMap->getResourceId()); + if (textureLocation >= 0) + { + std::static_pointer_cast(_viewer->_shadowBlurMaterial) + ->getProgram()->bindUniform("image_file", mx::Value::createValue(textureLocation)); + } + } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + renderScreenSpaceQuad(_viewer->_shadowBlurMaterial); + imageHandler->releaseRenderResources(_viewer->_shadowMap); + _viewer->_shadowMap = framebuffer->getColorImage(); + } + + // Restore state for scene rendering. + glViewport(0, 0, _viewer->m_fbsize[0], _viewer->m_fbsize[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDrawBuffer(GL_BACK); + } + } + + return _viewer->_shadowMap; +} + + +void GLRenderPipeline::renderFrame(void*, int shadowMapSize, const char* dirLightNodeCat) +{ + auto& genContext = _viewer->_genContext; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + auto& viewCamera = _viewer->_viewCamera; + auto& envCamera = _viewer->_envCamera; + auto& shadowCamera = _viewer->_shadowCamera; + float lightRotation = _viewer->_lightRotation; + auto& searchPath = _viewer->_searchPath; + auto& geometryHandler = _viewer->_geometryHandler; + + // Initialize OpenGL state + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LEQUAL); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glDisable(GL_CULL_FACE); + glDisable(GL_FRAMEBUFFER_SRGB); + + // Update lighting state. + lightHandler->setLightTransform(mx::Matrix44::createRotationY(lightRotation / 180.0f * PI)); + + // Update shadow state. + mx::ShadowState shadowState; + shadowState.ambientOcclusionGain = _viewer->_ambientOcclusionGain; + mx::NodePtr dirLight = lightHandler->getFirstLightOfCategory(dirLightNodeCat); + if (genContext.getOptions().hwShadowMap && dirLight) + { + mx::ImagePtr shadowMap = getShadowMap(shadowMapSize); + if (shadowMap) + { + shadowState.shadowMap = shadowMap; + shadowState.shadowMatrix = viewCamera->getWorldMatrix().getInverse() * + shadowCamera->getWorldViewProjMatrix(); + } + else + { + genContext.getOptions().hwShadowMap = false; + } + } + + glEnable(GL_FRAMEBUFFER_SRGB); + + // Environment background + if (_viewer->_drawEnvironment) + { + mx::MaterialPtr envMaterial = _viewer->getEnvironmentMaterial(); + if (envMaterial) + { + const mx::MeshList& meshes = _viewer->_envGeometryHandler->getMeshes(); + mx::MeshPartitionPtr envPart = !meshes.empty() ? meshes[0]->getPartition(0) : nullptr; + if (envPart) + { + // Apply rotation to the environment shader. + float longitudeOffset = (lightRotation / 360.0f) + 0.5f; + envMaterial->modifyUniform("longitude/in2", mx::Value::createValue(longitudeOffset)); + + // Render the environment mesh. + glDepthMask(GL_FALSE); + envMaterial->bindShader(); + envMaterial->bindMesh(meshes[0]); + envMaterial->bindViewInformation(envCamera); + envMaterial->bindImages(imageHandler, searchPath, false); + envMaterial->drawPartition(envPart); + glDepthMask(GL_TRUE); + } + } + else + { + _viewer->_drawEnvironment = false; + } + } + + // Enable backface culling if requested. + if (!_viewer->_renderDoubleSided) + { + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + } + + // Opaque pass + for (const auto& assignment : _viewer->_materialAssignments) + { + mx::MeshPartitionPtr geom = assignment.first; + mx::GlslMaterialPtr material = std::dynamic_pointer_cast(assignment.second); + shadowState.ambientOcclusionMap = _viewer->getAmbientOcclusionImage(material); + if (!material) + { + continue; + } + + material->bindShader(); + material->bindMesh(geometryHandler->findParentMesh(geom)); + if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD)) + { + material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.99f)); + } + material->bindViewInformation(viewCamera); + material->bindLighting(lightHandler, imageHandler, shadowState); + material->bindImages(imageHandler, searchPath); + material->drawPartition(geom); + material->unbindImages(imageHandler); + } + + // Transparent pass + if (_viewer->_renderTransparency) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + for (const auto& assignment : _viewer->_materialAssignments) + { + mx::MeshPartitionPtr geom = assignment.first; + mx::GlslMaterialPtr material = std::dynamic_pointer_cast(assignment.second); + shadowState.ambientOcclusionMap = _viewer->getAmbientOcclusionImage(material); + if (!material || !material->hasTransparency()) + { + continue; + } + + material->bindShader(); + material->bindMesh(geometryHandler->findParentMesh(geom)); + if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD)) + { + material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.001f)); + } + material->bindViewInformation(viewCamera); + material->bindLighting(lightHandler, imageHandler, shadowState); + material->bindImages(imageHandler, searchPath); + material->drawPartition(geom); + material->unbindImages(imageHandler); + } + glDisable(GL_BLEND); + } + + if (!_viewer->_renderDoubleSided) + { + glDisable(GL_CULL_FACE); + } + glDisable(GL_FRAMEBUFFER_SRGB); + + // Wireframe pass + if (_viewer->_outlineSelection) + { + mx::MaterialPtr wireMaterial = _viewer->getWireframeMaterial(); + if (wireMaterial) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + wireMaterial->bindShader(); + wireMaterial->bindMesh(geometryHandler->findParentMesh(_viewer->getSelectedGeometry())); + wireMaterial->bindViewInformation(viewCamera); + wireMaterial->drawPartition(_viewer->getSelectedGeometry()); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + else + { + _viewer->_outlineSelection = false; + } + } +} + +void GLRenderPipeline::bakeTextures() +{ + auto& imageHandler = _viewer->_imageHandler; + + mx::MaterialPtr material = _viewer->getSelectedMaterial(); + mx::DocumentPtr doc = material ? material->getDocument() : nullptr; + if (!doc) + { + return; + } + + { + // Compute baking resolution. + mx::ImageVec imageVec = imageHandler->getReferencedImages(doc); + auto maxImageSize = mx::getMaxDimensions(imageVec); + unsigned int bakeWidth = std::max(maxImageSize.first, (unsigned int) 4); + unsigned int bakeHeight = std::max(maxImageSize.second, (unsigned int) 4); + if (_viewer->_bakeWidth) + { + bakeWidth = std::max(_viewer->_bakeWidth, (unsigned int) 4); + } + if (_viewer->_bakeHeight) + { + bakeHeight = std::max(_viewer->_bakeHeight, (unsigned int) 4); + } + + // Construct a texture baker. + mx::Image::BaseType baseType = _viewer->_bakeHdr ? mx::Image::BaseType::FLOAT : mx::Image::BaseType::UINT8; + mx::TextureBakerPtr baker = std::static_pointer_cast(createTextureBaker(bakeWidth, bakeHeight, baseType)); + baker->setupUnitSystem(_viewer->_stdLib); + baker->setDistanceUnit(_viewer->_genContext.getOptions().targetDistanceUnit); + baker->setAverageImages(_viewer->_bakeAverage); + baker->setOptimizeConstants(_viewer->_bakeOptimize); + + // Assign our existing image handler, releasing any existing render resources for cached images. + imageHandler->releaseRenderResources(); + baker->setImageHandler(imageHandler); + + // Extend the image search path to include material source folders. + mx::FileSearchPath extendedSearchPath = _viewer->_searchPath; + extendedSearchPath.append(_viewer->_materialSearchPath); + + // Bake all materials in the active document. + try + { + baker->bakeAllMaterials(doc, extendedSearchPath, _viewer->_bakeFilename); + } + catch (std::exception& e) + { + std::cerr << "Error in texture baking: " << e.what() << std::endl; + } + + // Release any render resources generated by the baking process. + imageHandler->releaseRenderResources(); + } +} diff --git a/source/MaterialXView/RenderPipelineGL.h b/source/MaterialXView/RenderPipelineGL.h new file mode 100644 index 0000000000..781adf6a24 --- /dev/null +++ b/source/MaterialXView/RenderPipelineGL.h @@ -0,0 +1,47 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef RENDER_PIPELINE_GL_H +#define RENDER_PIPELINE_GL_H + +#include + +class Viewer; +using GLRenderPipelinePtr = std::shared_ptr; + +class GLRenderPipeline : public RenderPipeline +{ +public: + ~GLRenderPipeline() { } + + static GLRenderPipelinePtr create(Viewer* viewer) + { + return std::make_shared(viewer); + } + + void initialize(void* metal_device, void* metal_cmd_queue) override; + + void initFramebuffer(int width, int height, + void* color_texture) override; + void resizeFramebuffer(int width, int height, + void* color_texture) override; + + mx::ImageHandlerPtr createImageHandler() override; + mx::MaterialPtr createMaterial() override; + void updateAlbedoTable(int tableSize) override; + std::shared_ptr createTextureBaker(unsigned int width, + unsigned int height, + mx::Image::BaseType baseType) override; + void renderFrame(void* color_texture, int shadowMapSize, const char* dirLightNodeCat) override; + void bakeTextures() override; + +public: + GLRenderPipeline(Viewer* viewerPtr); + +protected: + mx::ImagePtr getShadowMap(int shadowMapSize) override; +}; + +#endif // RENDER_PIPELINE_GL_H diff --git a/source/MaterialXView/RenderPipelineMetal.h b/source/MaterialXView/RenderPipelineMetal.h new file mode 100644 index 0000000000..6ab8a055a7 --- /dev/null +++ b/source/MaterialXView/RenderPipelineMetal.h @@ -0,0 +1,52 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef RENDER_PIPELINE_METAL_H +#define RENDER_PIPELINE_METAL_H + +#include + +MATERIALX_NAMESPACE_BEGIN +using MetalFramebufferPtr = std::shared_ptr; +MATERIALX_NAMESPACE_END + +class Viewer; +using MetalRenderPipelinePtr = std::shared_ptr; + +class MetalRenderPipeline : public RenderPipeline +{ +public: + ~MetalRenderPipeline() { } + + static MetalRenderPipelinePtr create(Viewer* viewer) + { + return std::make_shared(viewer); + } + + std::shared_ptr createTextureBaker(unsigned int width, + unsigned int height, + mx::Image::BaseType baseType) override; + + void initialize(void* metal_device, void* metal_cmd_queue) override; + + void initFramebuffer(int width, int height, + void* color_texture) override; + void resizeFramebuffer(int width, int height, + void* color_texture) override; + mx::ImageHandlerPtr createImageHandler() override; + mx::MaterialPtr createMaterial() override; + void updateAlbedoTable(int tableSize) override; + void renderFrame(void* color_texture, int shadowMapSize, const char* dirLightNodeCat) override; + void bakeTextures() override; + +public: + MetalRenderPipeline(Viewer* viewerPtr); + +protected: + mx::ImagePtr getShadowMap(int shadowMapSize) override; + mx::MetalFramebufferPtr _shadowMapFramebuffer; +}; + +#endif // RENDER_PIPELINE_METAL_H diff --git a/source/MaterialXView/RenderPipelineMetal.mm b/source/MaterialXView/RenderPipelineMetal.mm new file mode 100644 index 0000000000..4d8e5c1c4a --- /dev/null +++ b/source/MaterialXView/RenderPipelineMetal.mm @@ -0,0 +1,581 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace +{ +const float PI = std::acos(-1.0f); +} + +MetalRenderPipeline::MetalRenderPipeline(Viewer* viewerPtr) : + RenderPipeline(viewerPtr) +{ +} + +void MetalRenderPipeline::initialize(void* metal_device, void* metal_cmd_queue) +{ + MTL(initialize((id)metal_device, + (id)metal_cmd_queue)); +} + +mx::ImageHandlerPtr MetalRenderPipeline::createImageHandler() +{ + return mx::MetalTextureHandler::create( + MTL(device), + mx::StbImageLoader::create()); +} + +mx::MaterialPtr MetalRenderPipeline::createMaterial() +{ + return mx::MslMaterial::create(); +} + +std::shared_ptr MetalRenderPipeline::createTextureBaker(unsigned int width, + unsigned int height, + mx::Image::BaseType baseType) +{ + return std::static_pointer_cast(mx::TextureBakerMsl::create(width, height, baseType)); +} + +void MetalRenderPipeline::initFramebuffer(int width, int height, + void* color_texture) +{ + MTL_PUSH_FRAMEBUFFER(mx::MetalFramebuffer::create( + MTL(device), + width * 2, + height * 2, + 4, mx::Image::BaseType::UINT8, + MTL(supportsTiledPipeline) ? + (id)color_texture : nil, + false, MTLPixelFormatBGRA8Unorm)); +} + +void MetalRenderPipeline::resizeFramebuffer(int width, int height, + void* color_texture) +{ + MTL_POP_FRAMEBUFFER(); + initFramebuffer(width, height, color_texture); +} + +void MetalRenderPipeline::updateAlbedoTable(int tableSize) +{ + auto& genContext = _viewer->_genContext; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + + if (lightHandler->getAlbedoTable()) + { + return; + } + + // Create framebuffer. + mx::MetalFramebufferPtr framebuffer = mx::MetalFramebuffer::create(MTL(device), + tableSize, tableSize, + 2, + mx::Image::BaseType::FLOAT); + + bool captureCommandBuffer = false; + if(captureCommandBuffer) + MTL_TRIGGER_CAPTURE; + + MTL_PUSH_FRAMEBUFFER(framebuffer); + + MTL(beginCommandBuffer()); + + MTLRenderPassDescriptor* renderpassDesc = [MTLRenderPassDescriptor new]; + + [renderpassDesc.colorAttachments[0] setTexture:framebuffer->getColorTexture()]; + [renderpassDesc.colorAttachments[0] setClearColor:MTLClearColorMake(0.0f, 0.0f, 0.0f, 0.0f)]; + [renderpassDesc.colorAttachments[0] setLoadAction:MTLLoadActionClear]; + [renderpassDesc.colorAttachments[0] setStoreAction:MTLStoreActionStore]; + + [renderpassDesc.depthAttachment setTexture:framebuffer->getDepthTexture()]; + [renderpassDesc.depthAttachment setClearDepth:1.0]; + [renderpassDesc.depthAttachment setLoadAction:MTLLoadActionClear]; + [renderpassDesc.depthAttachment setStoreAction:MTLStoreActionStore]; + [renderpassDesc setStencilAttachment:nil]; + + MTL(beginEncoder(renderpassDesc)); + + // Create shader. + mx::ShaderPtr hwShader = mx::createAlbedoTableShader(genContext, _viewer->_stdLib, "__ALBEDO_TABLE_SHADER__"); + mx::MslMaterialPtr material = mx::MslMaterial::create(); + try + { + material->generateShader(hwShader); + } + catch (std::exception& e) + { + new ng::MessageDialog(_viewer, ng::MessageDialog::Type::Warning, "Failed to generate albedo table shader", e.what()); + return; + } + + // Render albedo table. + material->bindShader(); + if (material->getProgram()->hasUniform(mx::HW::ALBEDO_TABLE_SIZE)) + { + material->getProgram()->bindUniform(mx::HW::ALBEDO_TABLE_SIZE, mx::Value::createValue(tableSize)); + } + material->getProgram()->prepareUsedResources( + MTL(renderCmdEncoder), + _viewer->_identityCamera, + nullptr, + imageHandler, + lightHandler); + renderScreenSpaceQuad(material); + + MTL(endCommandBuffer()); + + MTL_POP_FRAMEBUFFER(); + + if(captureCommandBuffer) + MTL_STOP_CAPTURE; + + // Store albedo table image. + imageHandler->releaseRenderResources(lightHandler->getAlbedoTable()); + lightHandler->setAlbedoTable(framebuffer->getColorImage(MTL(cmdQueue))); + if (_viewer->_saveGeneratedLights) + { + imageHandler->saveImage("AlbedoTable.exr", lightHandler->getAlbedoTable()); + } +} + +mx::ImagePtr MetalRenderPipeline::getShadowMap(int shadowMapSize) +{ + auto& genContext = _viewer->_genContext; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + auto& shadowCamera = _viewer->_shadowCamera; + auto& stdLib = _viewer->_stdLib; + auto& geometryHandler = _viewer->_geometryHandler; + auto& identityCamera = _viewer->_identityCamera; + + if (!_viewer->_shadowMap) + { + // Create framebuffer. + if(!_shadowMapFramebuffer) + { + _shadowMapFramebuffer = mx::MetalFramebuffer::create( + MTL(device), + shadowMapSize, + shadowMapSize, + 2, + mx::Image::BaseType::FLOAT); + } + MTL_PUSH_FRAMEBUFFER(_shadowMapFramebuffer); + + // Generate shaders for shadow rendering. + if (!_viewer->_shadowMaterial) + { + try + { + mx::ShaderPtr hwShader = mx::createDepthShader(genContext, stdLib, "__SHADOW_SHADER__"); + _viewer->_shadowMaterial = mx::MslMaterial::create(); + _viewer->_shadowMaterial->generateShader(hwShader); + } + catch (std::exception& e) + { + std::cerr << "Failed to generate shadow shader: " << e.what() << std::endl; + _viewer->_shadowMaterial = nullptr; + } + } + if (!_viewer->_shadowBlurMaterial) + { + try + { + mx::ShaderPtr hwShader = mx::createBlurShader(genContext, stdLib, "__SHADOW_BLUR_SHADER__", "gaussian", 1.0f); + _viewer->_shadowBlurMaterial = mx::MslMaterial::create(); + _viewer->_shadowBlurMaterial->generateShader(hwShader); + } + catch (std::exception& e) + { + std::cerr << "Failed to generate shadow blur shader: " << e.what() << std::endl; + _viewer->_shadowBlurMaterial = nullptr; + } + } + + if (_viewer->_shadowMaterial && _viewer->_shadowBlurMaterial) + { + bool captureShadowGeneration = false; + if(captureShadowGeneration) + MTL_TRIGGER_CAPTURE; + + MTL(beginCommandBuffer()); + MTLRenderPassDescriptor* renderpassDesc = [MTLRenderPassDescriptor new]; + _shadowMapFramebuffer->bind(renderpassDesc); + MTL(beginEncoder(renderpassDesc)); + [MTL(renderCmdEncoder) setDepthStencilState:MTL_DEPTHSTENCIL_STATE(opaque)]; + + // Render shadow geometry. + _viewer->_shadowMaterial->bindShader(); + for (auto mesh : _viewer->_geometryHandler->getMeshes()) + { + _viewer->_shadowMaterial->bindMesh(mesh); + _viewer->_shadowMaterial->bindViewInformation(shadowCamera); + std::static_pointer_cast + (_viewer->_shadowMaterial)->prepareUsedResources( + shadowCamera, + geometryHandler, + imageHandler, + lightHandler); + for (size_t i = 0; i < mesh->getPartitionCount(); i++) + { + mx::MeshPartitionPtr geom = mesh->getPartition(i); + _viewer->_shadowMaterial->drawPartition(geom); + } + } + + MTL(endCommandBuffer()); + + _viewer->_shadowMap = _shadowMapFramebuffer->getColorImage(MTL(cmdQueue)); + + MTL(beginCommandBuffer()); + MTL(beginEncoder(renderpassDesc)); + + // Apply Gaussian blurring. + mx::ImageSamplingProperties blurSamplingProperties; + blurSamplingProperties.uaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; + blurSamplingProperties.vaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; + blurSamplingProperties.filterType = mx::ImageSamplingProperties::FilterType::CLOSEST; + for (unsigned int i = 0; i < _viewer->_shadowSoftness; i++) + { + _shadowMapFramebuffer->bind(renderpassDesc); + _viewer->_shadowBlurMaterial->bindShader(); + std::static_pointer_cast + (_viewer->_shadowBlurMaterial)->getProgram()->bindTexture( + _viewer->_imageHandler, + "image_file_tex", + _viewer->_shadowMap, + blurSamplingProperties); + std::static_pointer_cast + (_viewer->_shadowBlurMaterial)->prepareUsedResources( + identityCamera, + geometryHandler, + imageHandler, + lightHandler); + _viewer->_shadowBlurMaterial->unbindGeometry(); + renderScreenSpaceQuad(_viewer->_shadowBlurMaterial); + _viewer->_imageHandler->releaseRenderResources(_viewer->_shadowMap); + _viewer->_shadowMap = _shadowMapFramebuffer->getColorImage(MTL(cmdQueue)); + } + + MTL(endCommandBuffer()); + + MTL_POP_FRAMEBUFFER(); + if(captureShadowGeneration) + MTL_STOP_CAPTURE; + } + } + + return _viewer->_shadowMap; +} + +void MetalRenderPipeline::renderFrame(void* color_texture, int shadowMapSize, const char* dirLightNodeCat) +{ + auto& genContext = _viewer->_genContext; + auto& lightHandler = _viewer->_lightHandler; + auto& imageHandler = _viewer->_imageHandler; + auto& viewCamera = _viewer->_viewCamera; + auto& envCamera = _viewer->_envCamera; + auto& shadowCamera = _viewer->_shadowCamera; + float lightRotation = _viewer->_lightRotation; + auto& searchPath = _viewer->_searchPath; + auto& geometryHandler = _viewer->_geometryHandler; + + // Update lighting state. + lightHandler->setLightTransform(mx::Matrix44::createRotationY(lightRotation / 180.0f * M_PI)); + + // Update shadow state. + mx::ShadowState shadowState; + shadowState.ambientOcclusionGain = _viewer->_ambientOcclusionGain; + mx::NodePtr dirLight = lightHandler->getFirstLightOfCategory(dirLightNodeCat); + if (genContext.getOptions().hwShadowMap && dirLight) + { + mx::ImagePtr shadowMap = getShadowMap(shadowMapSize); + if (shadowMap) + { + shadowState.shadowMap = shadowMap; + shadowState.shadowMatrix = viewCamera->getWorldMatrix().getInverse() * + shadowCamera->getWorldViewProjMatrix(); + } + else + { + genContext.getOptions().hwShadowMap = false; + } + } + + bool captureFrame = false; + if(captureFrame) + MTL_TRIGGER_CAPTURE; + + bool useTiledPipeline; + if(@available(macOS 11.0, ios 14.0, *)) + { + useTiledPipeline = MTL(supportsTiledPipeline); + } + else + { + useTiledPipeline = false; + } + + MTL(beginCommandBuffer()); + MTLRenderPassDescriptor* renderpassDesc = [MTLRenderPassDescriptor new]; + if(useTiledPipeline) + { + [renderpassDesc.colorAttachments[0] setTexture:(id)color_texture]; + } + else + { + [renderpassDesc.colorAttachments[0] setTexture:MTL(currentFramebuffer())->getColorTexture()]; + } + [renderpassDesc.colorAttachments[0] setClearColor:MTLClearColorMake( + _viewer->m_background[0], + _viewer->m_background[1], + _viewer->m_background[2], + _viewer->m_background[3])]; + [renderpassDesc.colorAttachments[0] setLoadAction:MTLLoadActionClear]; + [renderpassDesc.colorAttachments[0] setStoreAction:MTLStoreActionStore]; + + [renderpassDesc.depthAttachment setTexture:MTL(currentFramebuffer())->getDepthTexture()]; + [renderpassDesc.depthAttachment setClearDepth:1.0]; + [renderpassDesc.depthAttachment setLoadAction:MTLLoadActionClear]; + [renderpassDesc.depthAttachment setStoreAction:MTLStoreActionStore]; + [renderpassDesc setStencilAttachment:nil]; + + MTL(beginEncoder(renderpassDesc)); + + [MTL(renderCmdEncoder) setFrontFacingWinding:MTLWindingClockwise]; + + // Environment background + if (_viewer->_drawEnvironment) + { + [MTL(renderCmdEncoder) setDepthStencilState:MTL_DEPTHSTENCIL_STATE(envMap)]; + mx::MslMaterialPtr envMaterial = std::static_pointer_cast(_viewer->getEnvironmentMaterial()); + if (envMaterial) + { + const mx::MeshList& meshes = _viewer->_envGeometryHandler->getMeshes(); + mx::MeshPartitionPtr envPart = !meshes.empty() ? meshes[0]->getPartition(0) : nullptr; + if (envPart) + { + // Apply rotation to the environment shader. + float longitudeOffset = (lightRotation / 360.0f) + 0.5f; + envMaterial->modifyUniform("longitude/in2", mx::Value::createValue(longitudeOffset)); + + // Render the environment mesh. + [MTL(renderCmdEncoder) setCullMode:MTLCullModeNone]; + envMaterial->bindShader(); + envMaterial->bindMesh(meshes[0]); + envMaterial->bindViewInformation(envCamera); + envMaterial->bindImages(imageHandler, searchPath, false); + envMaterial->prepareUsedResources(envCamera, + _viewer->_envGeometryHandler, + imageHandler, + lightHandler); + envMaterial->drawPartition(envPart); + [MTL(renderCmdEncoder) setCullMode:MTLCullModeNone]; + } + } + else + { + _viewer->_drawEnvironment = false; + } + } + + // Enable backface culling if requested. + if (!_viewer->_renderDoubleSided) + { + [MTL(renderCmdEncoder) setCullMode:MTLCullModeBack]; + } + + // Opaque pass + [MTL(renderCmdEncoder) setDepthStencilState:MTL_DEPTHSTENCIL_STATE(opaque)]; + for (const auto& assignment : _viewer->_materialAssignments) + { + mx::MeshPartitionPtr geom = assignment.first; + mx::MslMaterialPtr material = std::static_pointer_cast(assignment.second); + shadowState.ambientOcclusionMap = _viewer->getAmbientOcclusionImage(material); + if (!material) + { + continue; + } + + material->bindShader(); + material->bindMesh(_viewer->_geometryHandler->findParentMesh(geom)); + if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD)) + { + material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.99f)); + } + material->bindViewInformation(viewCamera); + material->bindLighting(lightHandler, imageHandler, shadowState); + material->bindImages(imageHandler, _viewer->_searchPath); + material->prepareUsedResources(viewCamera, + geometryHandler, + imageHandler, + lightHandler); + material->drawPartition(geom); + material->unbindImages(imageHandler); + } + + // Transparent pass + if (_viewer->_renderTransparency) + { + [MTL(renderCmdEncoder) setDepthStencilState:MTL_DEPTHSTENCIL_STATE(transparent)]; + for (const auto& assignment : _viewer->_materialAssignments) + { + mx::MeshPartitionPtr geom = assignment.first; + mx::MslMaterialPtr material = std::static_pointer_cast(assignment.second); + shadowState.ambientOcclusionMap = _viewer->getAmbientOcclusionImage(material); + if (!material || !material->hasTransparency()) + { + continue; + } + + material->bindShader(); + material->bindMesh(geometryHandler->findParentMesh(geom)); + if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD)) + { + material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.001f)); + } + material->bindViewInformation(viewCamera); + material->bindLighting(lightHandler, imageHandler, shadowState); + material->bindImages(imageHandler, searchPath); + material->prepareUsedResources(viewCamera, + geometryHandler, + imageHandler, + lightHandler); + material->drawPartition(geom); + material->unbindImages(imageHandler); + } + } + + if (!_viewer->_renderDoubleSided) + { + [MTL(renderCmdEncoder) setCullMode:MTLCullModeNone]; + } + + // Wireframe pass + if (_viewer->_outlineSelection) + { + mx::MslMaterialPtr wireMaterial = + std::static_pointer_cast(_viewer->getWireframeMaterial()); + if (wireMaterial) + { + [MTL(renderCmdEncoder) setCullMode:MTLCullModeNone]; + [MTL(renderCmdEncoder) setTriangleFillMode:MTLTriangleFillModeLines]; + wireMaterial->bindShader(); + wireMaterial->bindMesh(geometryHandler->findParentMesh(_viewer->getSelectedGeometry())); + wireMaterial->bindViewInformation(viewCamera); + wireMaterial->prepareUsedResources(viewCamera, + geometryHandler, + imageHandler, + lightHandler); + wireMaterial->drawPartition(_viewer->getSelectedGeometry()); + [MTL(renderCmdEncoder) setTriangleFillMode:MTLTriangleFillModeFill]; + [MTL(renderCmdEncoder) setCullMode:MTLCullModeNone]; + } + else + { + _viewer->_outlineSelection = false; + } + } + +#ifdef MAC_OS_VERSION_11_0 + if(useTiledPipeline) + { + if(@available(macOS 11.0, ios 14.0, *)) + { + [MTL(renderCmdEncoder) setRenderPipelineState:MTL(linearToSRGB_pso)]; + [MTL(renderCmdEncoder) dispatchThreadsPerTile:MTLSizeMake( + MTL(renderCmdEncoder).tileWidth, + MTL(renderCmdEncoder).tileHeight, 1)]; + } + } + + if(!useTiledPipeline) +#endif + { + MTL(endEncoder()); + [renderpassDesc.colorAttachments[0] setTexture:(id)color_texture]; + MTL(beginEncoder(renderpassDesc)); + [MTL(renderCmdEncoder) setRenderPipelineState:MTL(linearToSRGB_pso)]; + [MTL(renderCmdEncoder) + setFragmentTexture:MTL(currentFramebuffer())->getColorTexture() + atIndex:0]; + [MTL(renderCmdEncoder) drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; + } + + MTL(endCommandBuffer()); + + if(captureFrame) + MTL_STOP_CAPTURE; +} + +void MetalRenderPipeline::bakeTextures() +{ + auto& imageHandler = _viewer->_imageHandler; + + mx::MaterialPtr material = _viewer->getSelectedMaterial(); + mx::DocumentPtr doc = material ? material->getDocument() : nullptr; + if (!doc) + { + return; + } + + { + // Compute baking resolution. + mx::ImageVec imageVec = imageHandler->getReferencedImages(doc); + auto maxImageSize = mx::getMaxDimensions(imageVec); + unsigned int bakeWidth = std::max(maxImageSize.first, (unsigned int) 4); + unsigned int bakeHeight = std::max(maxImageSize.second, (unsigned int) 4); + if (_viewer->_bakeWidth) + { + bakeWidth = std::max(_viewer->_bakeWidth, (unsigned int) 4); + } + if (_viewer->_bakeHeight) + { + bakeHeight = std::max(_viewer->_bakeHeight, (unsigned int) 4); + } + + // Construct a texture baker. + mx::Image::BaseType baseType = _viewer->_bakeHdr ? mx::Image::BaseType::FLOAT : mx::Image::BaseType::UINT8; + mx::TextureBakerPtr baker = std::static_pointer_cast(createTextureBaker(bakeWidth, bakeHeight, baseType)); + baker->setupUnitSystem(_viewer->_stdLib); + baker->setDistanceUnit(_viewer->_genContext.getOptions().targetDistanceUnit); + baker->setAverageImages(_viewer->_bakeAverage); + baker->setOptimizeConstants(_viewer->_bakeOptimize); + + // Assign our existing image handler, releasing any existing render resources for cached images. + imageHandler->releaseRenderResources(); + baker->setImageHandler(imageHandler); + + // Extend the image search path to include material source folders. + mx::FileSearchPath extendedSearchPath = _viewer->_searchPath; + extendedSearchPath.append(_viewer->_materialSearchPath); + + // Bake all materials in the active document. + try + { + baker->bakeAllMaterials(doc, extendedSearchPath, _viewer->_bakeFilename); + } + catch (std::exception& e) + { + std::cerr << "Error in texture baking: " << e.what() << std::endl; + } + + // Release any render resources generated by the baking process. + imageHandler->releaseRenderResources(); + } +} diff --git a/source/MaterialXView/Viewer.cpp b/source/MaterialXView/Viewer.cpp index a0de58e34f..1b04586537 100644 --- a/source/MaterialXView/Viewer.cpp +++ b/source/MaterialXView/Viewer.cpp @@ -4,10 +4,19 @@ // #include +#include +#ifdef MATERIALXVIEW_METAL_BACKEND +#include +#include +#include +#else #include -#include +#include +#include +#endif +#include #include #include #include @@ -52,11 +61,10 @@ const int ALBEDO_TABLE_SIZE = 128; const int IRRADIANCE_MAP_WIDTH = 256; const int IRRADIANCE_MAP_HEIGHT = 128; -const float ORTHO_VIEW_DISTANCE = 1000.0f; -const float ORTHO_PROJECTION_HEIGHT = 1.8f; - const std::string DIR_LIGHT_NODE_CATEGORY = "directional_light"; const std::string IRRADIANCE_MAP_FOLDER = "irradiance"; +const float ORTHO_VIEW_DISTANCE = 1000.0f; +const float ORTHO_PROJECTION_HEIGHT = 1.8f; const float ENV_MAP_SPLIT_RADIANCE = 16.0f; const float MAX_ENV_TEXEL_RADIANCE = 100000.0f; @@ -151,12 +159,13 @@ void applyModifiers(mx::DocumentPtr doc, const DocumentModifiers& modifiers) // ViewDir implementation for GLSL // as needed for the environment shader. -class ViewDirGlsl : public mx::GlslImplementation +template +class ViewDir : public NodeGraphImpl { public: static mx::ShaderNodeImplPtr create() { - return std::make_shared(); + return std::make_shared(); } void createVariables(const mx::ShaderNode&, mx::GenContext&, mx::Shader& shader) const override @@ -244,12 +253,17 @@ Viewer::Viewer(const std::string& materialFilename, _selectedMaterial(0), _materialLabel(nullptr), _materialSelectionBox(nullptr), + _identityCamera(mx::Camera::create()), _viewCamera(mx::Camera::create()), _envCamera(mx::Camera::create()), _shadowCamera(mx::Camera::create()), _lightHandler(mx::LightHandler::create()), +#ifndef MATERIALXVIEW_METAL_BACKEND _genContext(mx::GlslShaderGenerator::create()), _genContextEssl(mx::EsslShaderGenerator::create()), +#else + _genContext(mx::MslShaderGenerator::create()), +#endif #if MATERIALX_BUILD_GEN_OSL _genContextOsl(mx::OslShaderGenerator::create()), #endif @@ -261,6 +275,7 @@ Viewer::Viewer(const std::string& materialFilename, _outlineSelection(false), _renderTransparency(true), _renderDoubleSided(true), + _colorTexture(nullptr), _splitByUdims(true), _mergeMaterials(false), _showAllInputs(false), @@ -296,12 +311,21 @@ Viewer::Viewer(const std::string& materialFilename, _genContext.getOptions().fileTextureVerticalFlip = true; _genContext.getOptions().hwShadowMap = true; _genContext.getOptions().hwImplicitBitangents = false; - + +#ifdef MATERIALXVIEW_METAL_BACKEND + _renderPipeline = MetalRenderPipeline::create(this); + _renderPipeline->initialize(ng::metal_device(), + ng::metal_command_queue()); + _genContext.getShaderGenerator().registerImplementation("IM_viewdir_vector3_" + _genContext.getShaderGenerator().getTarget(), ViewDir::create); +#else + _renderPipeline = GLRenderPipeline::create(this); + _genContext.getShaderGenerator().registerImplementation("IM_viewdir_vector3_" + _genContext.getShaderGenerator().getTarget(), ViewDir::create); + // Set Essl generator options _genContextEssl.getOptions().targetColorSpaceOverride = "lin_rec709"; _genContextEssl.getOptions().fileTextureVerticalFlip = false; _genContextEssl.getOptions().hwMaxActiveLightSources = 1; - +#endif #if MATERIALX_BUILD_GEN_OSL // Set OSL generator options. _genContextOsl.getOptions().targetColorSpaceOverride = "lin_rec709"; @@ -312,9 +336,7 @@ Viewer::Viewer(const std::string& materialFilename, _genContextMdl.getOptions().targetColorSpaceOverride = "lin_rec709"; _genContextMdl.getOptions().fileTextureVerticalFlip = false; #endif - - // Register the GLSL implementation for used by the environment shader. - _genContext.getShaderGenerator().registerImplementation("IM_viewdir_vector3_" + mx::GlslShaderGenerator::TARGET, ViewDirGlsl::create); + // Register the API Spcefic implementation for used by the environment shader. } void Viewer::initialize() @@ -327,7 +349,7 @@ void Viewer::initialize() loadStandardLibraries(); // Initialize image handler. - _imageHandler = mx::GLTextureHandler::create(mx::StbImageLoader::create()); + _imageHandler = _renderPipeline->createImageHandler(); #if MATERIALX_BUILD_OIIO _imageHandler->addLoader(mx::OiioImageLoader::create()); #endif @@ -384,6 +406,8 @@ void Viewer::initialize() _geometryHandler->addLoader(gltfLoader); loadMesh(_searchPath.find(_meshFilename)); + _renderPipeline->initFramebuffer(width(), height(), nullptr); + // Create environment geometry handler. _envGeometryHandler = mx::GeometryHandler::create(); _envGeometryHandler->addLoader(objLoader); @@ -397,12 +421,16 @@ void Viewer::initialize() initCamera(); set_resize_callback([this](ng::Vector2i size) { +#ifdef MATERIALXVIEW_METAL_BACKEND + _colorTexture = metal_texture(); +#endif + _renderPipeline->resizeFramebuffer(size.x(), size.y(), _colorTexture); _viewCamera->setViewportSize(mx::Vector2(static_cast(size[0]), static_cast(size[1]))); }); // Update geometry selections. updateGeometrySelections(); - + // Load the requested material document. loadDocument(_materialFilename, _stdLib); @@ -510,12 +538,14 @@ void Viewer::applyDirectLights(mx::DocumentPtr doc) _xincludeFiles.insert(_lightRigFilename); } - try + try { std::vector lights; _lightHandler->findLights(doc, lights); _lightHandler->registerLights(doc, lights, _genContext); +#ifndef MATERIALXVIEW_METAL_BACKEND _lightHandler->registerLights(doc, lights, _genContextEssl); +#endif _lightHandler->setLightSources(lights); } catch (std::exception& e) @@ -524,7 +554,7 @@ void Viewer::applyDirectLights(mx::DocumentPtr doc) } } -void Viewer::assignMaterial(mx::MeshPartitionPtr geometry, mx::GlslMaterialPtr material) +void Viewer::assignMaterial(mx::MeshPartitionPtr geometry, mx::MaterialPtr material) { if (!geometry || _geometryHandler->getMeshes().empty()) { @@ -586,7 +616,7 @@ void Viewer::createLoadMeshInterface(Widget* parent, const std::string& label) { { "obj", "Wavefront OBJ" }, { "gltf", "GLTF ASCII" }, - { "glb", "GLTF Binary"} + { "glb", "GLTF Binary"} }, false); if (!filename.empty()) { @@ -651,7 +681,7 @@ void Viewer::createSaveMaterialsInterface(Widget* parent, const std::string& lab materialButton->set_callback([this]() { m_process_events = false; - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::FilePath filename = ng::file_dialog({ { "mtlx", "MaterialX" } }, true); // Save document @@ -738,7 +768,9 @@ void Viewer::createAdvancedSettings(Widget* parent) importanceSampleBox->set_callback([this](bool enable) { _genContext.getOptions().hwSpecularEnvironmentMethod = enable ? mx::SPECULAR_ENVIRONMENT_FIS : mx::SPECULAR_ENVIRONMENT_PREFILTER; +#ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.getOptions().hwSpecularEnvironmentMethod = _genContext.getOptions().hwSpecularEnvironmentMethod; +#endif reloadShaders(); }); @@ -747,7 +779,9 @@ void Viewer::createAdvancedSettings(Widget* parent) refractionBox->set_callback([this](bool enable) { _genContext.getOptions().hwTransmissionRenderMethod = enable ? mx::TRANSMISSION_REFRACTION : mx::TRANSMISSION_OPACITY; +#ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.getOptions().hwTransmissionRenderMethod = _genContext.getOptions().hwTransmissionRenderMethod; +#endif reloadShaders(); }); @@ -764,7 +798,7 @@ void Viewer::createAdvancedSettings(Widget* parent) { mx::ShaderInterfaceType interfaceType = enable ? mx::SHADER_INTERFACE_REDUCED : mx::SHADER_INTERFACE_COMPLETE; setShaderInterfaceType(interfaceType); - }); + }); Widget* albedoGroup = new Widget(advancedPopup); albedoGroup->set_layout(new ng::BoxLayout(ng::Orientation::Horizontal)); @@ -777,7 +811,7 @@ void Viewer::createAdvancedSettings(Widget* parent) { _genContext.getOptions().hwDirectionalAlbedoMethod = (mx::HwDirectionalAlbedoMethod) index; reloadShaders(); - updateAlbedoTable(); + _renderPipeline->updateAlbedoTable(ALBEDO_TABLE_SIZE); }); Widget* sampleGroup = new Widget(advancedPopup); @@ -876,14 +910,16 @@ void Viewer::createAdvancedSettings(Widget* parent) { m_process_events = false; _genContext.getOptions().targetDistanceUnit = _distanceUnitOptions[index]; +#ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.getOptions().targetDistanceUnit = _distanceUnitOptions[index]; +#endif #if MATERIALX_BUILD_GEN_OSL _genContextOsl.getOptions().targetDistanceUnit = _distanceUnitOptions[index]; #endif #if MATERIALX_BUILD_GEN_MDL _genContextMdl.getOptions().targetDistanceUnit = _distanceUnitOptions[index]; #endif - for (mx::GlslMaterialPtr material : _materials) + for (mx::MaterialPtr material : _materials) { material->bindUnits(_unitRegistry, _genContext); } @@ -924,7 +960,7 @@ void Viewer::createAdvancedSettings(Widget* parent) flattenBox->set_callback([this](bool enable) { _flattenSubgraphs = enable; - }); + }); ng::Label* envLoading = new ng::Label(advancedPopup, "Environment Loading Options"); envLoading->set_font_size(20); @@ -1128,7 +1164,7 @@ void Viewer::loadMesh(const mx::FilePath& filename) { _meshFilename = filename; if (_splitByUdims) - { + { for (auto mesh : _geometryHandler->getMeshes()) { mesh->splitByUdims(); @@ -1139,7 +1175,7 @@ void Viewer::loadMesh(const mx::FilePath& filename) // Assign the selected material to all geometries. _materialAssignments.clear(); - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); if (material) { for (mx::MeshPartitionPtr geom : _geometryList) @@ -1186,7 +1222,9 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie // Clear user data on the generator. _genContext.clearUserData(); +#ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.clearUserData(); +#endif // Clear materials if merging is not requested. if (!_mergeMaterials) @@ -1201,7 +1239,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie _materials.clear(); } - std::vector newMaterials; + std::vector newMaterials; try { // Load source document. @@ -1282,7 +1320,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie { for (const std::string& udim : udimSetValue->asA()) { - mx::GlslMaterialPtr mat = mx::GlslMaterial::create(); + mx::MaterialPtr mat = _renderPipeline->createMaterial(); mat->setDocument(doc); mat->setElement(typedElem); mat->setMaterialNode(materialNodes[i]); @@ -1294,7 +1332,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie } else { - mx::GlslMaterialPtr mat = mx::GlslMaterial::create(); + mx::MaterialPtr mat = _renderPipeline->createMaterial(); mat->setDocument(doc); mat->setElement(typedElem); mat->setMaterialNode(materialNodes[i]); @@ -1312,12 +1350,14 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie // Add new materials to the global vector. _materials.insert(_materials.end(), newMaterials.begin(), newMaterials.end()); - mx::GlslMaterialPtr udimMaterial = nullptr; - for (mx::GlslMaterialPtr mat : newMaterials) + mx::MaterialPtr udimMaterial = nullptr; + for (mx::MaterialPtr mat : newMaterials) { // Clear cached implementations, in case libraries on the file system have changed. _genContext.clearNodeImplementations(); +#ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.clearNodeImplementations(); +#endif mx::TypedElementPtr elem = mat->getElement(); @@ -1362,7 +1402,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie } if (mx::geomStringsMatch(activeGeom, geom, true)) { - for (mx::GlslMaterialPtr mat : newMaterials) + for (mx::MaterialPtr mat : newMaterials) { if (mat->getMaterialNode() == matAssign->getReferencedMaterial()) { @@ -1374,7 +1414,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie mx::CollectionPtr coll = matAssign->getCollection(); if (coll && coll->matchesGeomString(geom)) { - for (mx::GlslMaterialPtr mat : newMaterials) + for (mx::MaterialPtr mat : newMaterials) { if (mat->getMaterialNode() == matAssign->getReferencedMaterial()) { @@ -1388,7 +1428,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie } // Apply implicit udim assignments, if any. - for (mx::GlslMaterialPtr mat : newMaterials) + for (mx::MaterialPtr mat : newMaterials) { mx::NodePtr materialNode = mat->getMaterialNode(); if (materialNode) @@ -1408,7 +1448,7 @@ void Viewer::loadDocument(const mx::FilePath& filename, mx::DocumentPtr librarie } // Apply fallback assignments. - mx::GlslMaterialPtr fallbackMaterial = newMaterials[0]; + mx::MaterialPtr fallbackMaterial = newMaterials[0]; if (!_mergeMaterials || fallbackMaterial->getUdim().empty()) { for (mx::MeshPartitionPtr geom : _geometryList) @@ -1448,7 +1488,7 @@ void Viewer::reloadShaders() { try { - for (mx::GlslMaterialPtr material : _materials) + for (mx::MaterialPtr material : _materials) { material->generateShader(_genContext); } @@ -1474,11 +1514,12 @@ void Viewer::saveShaderSource(mx::GenContext& context) { try { - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::TypedElementPtr elem = material ? material->getElement() : nullptr; if (elem) { mx::FilePath sourceFilename = getBaseOutputPath(); +#ifndef MATERIALXVIEW_METAL_BACKEND if (context.getShaderGenerator().getTarget() == mx::GlslShaderGenerator::TARGET) { mx::ShaderPtr shader = material->getShader(); @@ -1499,6 +1540,18 @@ void Viewer::saveShaderSource(mx::GenContext& context) new ng::MessageDialog(this, ng::MessageDialog::Type::Information, "Saved ESSL source: ", sourceFilename.asString() + "_essl_*.glsl"); } +#else + if (context.getShaderGenerator().getTarget() == mx::MslShaderGenerator::TARGET) + { + mx::ShaderPtr shader = material->getShader(); + const std::string& pixelShader = shader->getSourceCode(mx::Stage::PIXEL); + const std::string& vertexShader = shader->getSourceCode(mx::Stage::VERTEX); + writeTextFile(pixelShader, sourceFilename.asString() + "_ps.metal"); + writeTextFile(vertexShader, sourceFilename.asString() + "_vs.metal"); + new ng::MessageDialog(this, ng::MessageDialog::Type::Information, "Saved GLSL source: ", + sourceFilename.asString() + "_*.metal"); + } +#endif #if MATERIALX_BUILD_GEN_OSL else if (context.getShaderGenerator().getTarget() == mx::OslShaderGenerator::TARGET) { @@ -1531,7 +1584,7 @@ void Viewer::loadShaderSource() { try { - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::TypedElementPtr elem = material ? material->getElement() : nullptr; if (elem) { @@ -1554,7 +1607,7 @@ void Viewer::saveDotFiles() { try { - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::TypedElementPtr elem = material ? material->getElement() : nullptr; mx::NodePtr shaderNode = elem->asA(); if (shaderNode) @@ -1592,7 +1645,7 @@ void Viewer::saveDotFiles() mx::DocumentPtr Viewer::translateMaterial() { - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::DocumentPtr doc = material ? material->getDocument() : nullptr; if (!doc) { @@ -1669,7 +1722,9 @@ void Viewer::loadStandardLibraries() // Initialize the generator contexts. initContext(_genContext); +#ifndef MATERIALXVIEW_METAL_BACKEND initContext(_genContextEssl); +#endif #if MATERIALX_BUILD_GEN_OSL initContext(_genContextOsl); #endif @@ -1702,7 +1757,7 @@ bool Viewer::keyboard_event(int key, int scancode, int action, int modifiers) // the file system. if (key == GLFW_KEY_R && action == GLFW_PRESS) { - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::DocumentPtr doc = material ? material->getDocument() : nullptr; mx::FilePath filename = doc ? mx::FilePath(doc->getSourceUri()) : _materialFilename; if (modifiers == GLFW_MOD_SHIFT) @@ -1741,7 +1796,9 @@ bool Viewer::keyboard_event(int key, int scancode, int action, int modifiers) // Save Essl shader source to file. if (key == GLFW_KEY_E && action == GLFW_PRESS) { +#ifndef MATERIALXVIEW_METAL_BACKEND saveShaderSource(_genContextEssl); +#endif return true; } @@ -1875,163 +1932,6 @@ bool Viewer::keyboard_event(int key, int scancode, int action, int modifiers) return false; } -void Viewer::renderFrame() -{ - if (_geometryList.empty() || _materialAssignments.empty()) - { - return; - } - - // Initialize OpenGL state - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_TRUE); - glDepthFunc(GL_LEQUAL); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glDisable(GL_CULL_FACE); - glDisable(GL_FRAMEBUFFER_SRGB); - - // Update lighting state. - _lightHandler->setLightTransform(mx::Matrix44::createRotationY(_lightRotation / 180.0f * PI)); - - // Update shadow state. - mx::ShadowState shadowState; - shadowState.ambientOcclusionGain = _ambientOcclusionGain; - mx::NodePtr dirLight = _lightHandler->getFirstLightOfCategory(DIR_LIGHT_NODE_CATEGORY); - if (_genContext.getOptions().hwShadowMap && dirLight) - { - mx::ImagePtr shadowMap = getShadowMap(); - if (shadowMap) - { - shadowState.shadowMap = shadowMap; - shadowState.shadowMatrix = _viewCamera->getWorldMatrix().getInverse() * - _shadowCamera->getWorldViewProjMatrix(); - } - else - { - _genContext.getOptions().hwShadowMap = false; - } - } - - glEnable(GL_FRAMEBUFFER_SRGB); - - // Environment background - if (_drawEnvironment) - { - mx::GlslMaterialPtr envMaterial = getEnvironmentMaterial(); - if (envMaterial) - { - const mx::MeshList& meshes = _envGeometryHandler->getMeshes(); - mx::MeshPartitionPtr envPart = !meshes.empty() ? meshes[0]->getPartition(0) : nullptr; - if (envPart) - { - // Apply rotation to the environment shader. - float longitudeOffset = (_lightRotation / 360.0f) + 0.5f; - _envMaterial->modifyUniform("longitude/in2", mx::Value::createValue(longitudeOffset)); - - // Render the environment mesh. - glDepthMask(GL_FALSE); - envMaterial->bindShader(); - envMaterial->bindMesh(meshes[0]); - envMaterial->bindViewInformation(_envCamera); - envMaterial->bindImages(_imageHandler, _searchPath, false); - envMaterial->drawPartition(envPart); - glDepthMask(GL_TRUE); - } - } - else - { - _drawEnvironment = false; - } - } - - // Enable backface culling if requested. - if (!_renderDoubleSided) - { - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - } - - // Opaque pass - for (const auto& assignment : _materialAssignments) - { - mx::MeshPartitionPtr geom = assignment.first; - mx::GlslMaterialPtr material = assignment.second; - shadowState.ambientOcclusionMap = getAmbientOcclusionImage(material); - if (!material) - { - continue; - } - - material->bindShader(); - material->bindMesh(_geometryHandler->findParentMesh(geom)); - if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD)) - { - material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.99f)); - } - material->bindViewInformation(_viewCamera); - material->bindLighting(_lightHandler, _imageHandler, shadowState); - material->bindImages(_imageHandler, _searchPath); - material->drawPartition(geom); - material->unbindImages(_imageHandler); - } - - // Transparent pass - if (_renderTransparency) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - for (const auto& assignment : _materialAssignments) - { - mx::MeshPartitionPtr geom = assignment.first; - mx::GlslMaterialPtr material = assignment.second; - shadowState.ambientOcclusionMap = getAmbientOcclusionImage(material); - if (!material || !material->hasTransparency()) - { - continue; - } - - material->bindShader(); - material->bindMesh(_geometryHandler->findParentMesh(geom)); - if (material->getProgram()->hasUniform(mx::HW::ALPHA_THRESHOLD)) - { - material->getProgram()->bindUniform(mx::HW::ALPHA_THRESHOLD, mx::Value::createValue(0.001f)); - } - material->bindViewInformation(_viewCamera); - material->bindLighting(_lightHandler, _imageHandler, shadowState); - material->bindImages(_imageHandler, _searchPath); - material->drawPartition(geom); - material->unbindImages(_imageHandler); - } - glDisable(GL_BLEND); - } - - if (!_renderDoubleSided) - { - glDisable(GL_CULL_FACE); - } - glDisable(GL_FRAMEBUFFER_SRGB); - - // Wireframe pass - if (_outlineSelection) - { - mx::GlslMaterialPtr wireMaterial = getWireframeMaterial(); - if (wireMaterial) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - wireMaterial->bindShader(); - wireMaterial->bindMesh(_geometryHandler->findParentMesh(getSelectedGeometry())); - wireMaterial->bindViewInformation(_viewCamera); - wireMaterial->drawPartition(getSelectedGeometry()); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - else - { - _outlineSelection = false; - } - } -} - mx::ImagePtr Viewer::getFrameImage() { glFlush(); @@ -2049,7 +1949,7 @@ mx::ImagePtr Viewer::getFrameImage() mx::ImagePtr Viewer::renderWedge() { - mx::GlslMaterialPtr material = getSelectedMaterial(); + mx::MaterialPtr material = getSelectedMaterial(); mx::ShaderPort* uniform = material ? material->findUniform(_wedgePropertyName) : nullptr; if (!uniform) { @@ -2105,7 +2005,16 @@ mx::ImagePtr Viewer::renderWedge() } if (setValue) { - renderFrame(); +#ifdef MATERIALXVIEW_METAL_BACKEND + _colorTexture = metal_texture(); +#endif + if (!_geometryList.empty() && !_materialAssignments.empty()) + { + _renderPipeline->renderFrame(_colorTexture, + SHADOW_MAP_SIZE, + DIR_LIGHT_NODE_CATEGORY.c_str()); + } + imageVec.push_back(getFrameImage()); } } @@ -2117,68 +2026,6 @@ mx::ImagePtr Viewer::renderWedge() return nullptr; } -void Viewer::bakeTextures() -{ - mx::GlslMaterialPtr material = getSelectedMaterial(); - mx::DocumentPtr doc = material ? material->getDocument() : nullptr; - if (!doc) - { - return; - } - - { - // Compute baking resolution. - mx::ImageVec imageVec = _imageHandler->getReferencedImages(doc); - auto maxImageSize = mx::getMaxDimensions(imageVec); - unsigned int bakeWidth = std::max(maxImageSize.first, (unsigned int) 4); - unsigned int bakeHeight = std::max(maxImageSize.second, (unsigned int) 4); - if (_bakeWidth) - { - bakeWidth = std::max(_bakeWidth, (unsigned int) 4); - } - if (_bakeHeight) - { - bakeHeight = std::max(_bakeHeight, (unsigned int) 4); - } - - // Construct a texture baker. - mx::Image::BaseType baseType = _bakeHdr ? mx::Image::BaseType::FLOAT : mx::Image::BaseType::UINT8; - mx::TextureBakerPtr baker = mx::TextureBaker::create(bakeWidth, bakeHeight, baseType); - baker->setupUnitSystem(_stdLib); - baker->setDistanceUnit(_genContext.getOptions().targetDistanceUnit); - baker->setAverageImages(_bakeAverage); - baker->setOptimizeConstants(_bakeOptimize); - - // Assign our existing image handler, releasing any existing render resources for cached images. - _imageHandler->releaseRenderResources(); - baker->setImageHandler(_imageHandler); - - // Extend the image search path to include material source folders. - mx::FileSearchPath extendedSearchPath = _searchPath; - extendedSearchPath.append(_materialSearchPath); - - // Bake all materials in the active document. - try - { - baker->bakeAllMaterials(doc, extendedSearchPath, _bakeFilename); - } - catch (std::exception& e) - { - std::cerr << "Error in texture baking: " << e.what() << std::endl; - } - - // Release any render resources generated by the baking process. - _imageHandler->releaseRenderResources(); - } - - // After the baker has been destructed, restore state for scene rendering. - glfwMakeContextCurrent(m_glfw_window); - glfwGetFramebufferSize(m_glfw_window, &m_fbsize[0], &m_fbsize[1]); - glViewport(0, 0, m_fbsize[0], m_fbsize[1]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); -} - void Viewer::renderTurnable() { int frameCount = abs(_turntableSteps); @@ -2196,7 +2043,9 @@ void Viewer::renderTurnable() updateCameras(); clear(); invalidateShadowMap(); - renderFrame(); + _renderPipeline->renderFrame(_colorTexture, + SHADOW_MAP_SIZE, + DIR_LIGHT_NODE_CATEGORY.c_str()); mx::ImagePtr frameImage = getFrameImage(); if (frameImage) @@ -2216,13 +2065,15 @@ void Viewer::renderTurnable() } void Viewer::draw_contents() -{ +{ updateCameras(); - + +#ifndef MATERIALXVIEW_METAL_BACKEND mx::checkGlErrors("before viewer render"); - + // Clear the screen. clear(); +#endif if (_turntableEnabled && _turntableSteps) { @@ -2262,14 +2113,18 @@ void Viewer::draw_contents() // Render the current frame. try { - renderFrame(); + _renderPipeline->renderFrame(_colorTexture, + SHADOW_MAP_SIZE, + DIR_LIGHT_NODE_CATEGORY.c_str()); } catch (std::exception& e) { new ng::MessageDialog(this, ng::MessageDialog::Type::Warning, "Failed to render frame: ", e.what()); _materialAssignments.clear(); +#ifndef MATERIALXVIEW_METAL_BACKEND glDisable(GL_FRAMEBUFFER_SRGB); +#endif } // Capture the current frame. @@ -2292,7 +2147,7 @@ void Viewer::draw_contents() if (_bakeRequested) { _bakeRequested = false; - bakeTextures(); + _renderPipeline->bakeTextures(); } // Handle exit requests. @@ -2302,7 +2157,9 @@ void Viewer::draw_contents() set_visible(false); } +#ifndef MATERIALXVIEW_METAL_BACKEND mx::checkGlErrors("after viewer render"); +#endif } bool Viewer::scroll_event(const ng::Vector2i& p, const ng::Vector2f& rel) @@ -2416,6 +2273,13 @@ void Viewer::initCamera() void Viewer::updateCameras() { +#ifdef MATERIALXVIEW_METAL_BACKEND + auto& createPerspectiveMatrix = mx::Camera::createPerspectiveMatrixZP; + auto& createOrthographicMatrix = mx::Camera::createOrthographicMatrixZP; +#else + auto& createPerspectiveMatrix = mx::Camera::createPerspectiveMatrix; + auto& createOrthographicMatrix = mx::Camera::createOrthographicMatrix; +#endif mx::Matrix44 viewMatrix, projectionMatrix; float aspectRatio = (float) m_size.x() / (float) m_size.y(); if (_cameraViewAngle != 0.0f) @@ -2423,16 +2287,19 @@ void Viewer::updateCameras() viewMatrix = mx::Camera::createViewMatrix(_cameraPosition, _cameraTarget, _cameraUp); float fH = std::tan(_cameraViewAngle / 360.0f * PI) * _cameraNearDist; float fW = fH * aspectRatio; - projectionMatrix = mx::Camera::createPerspectiveMatrix(-fW, fW, -fH, fH, _cameraNearDist, _cameraFarDist); + projectionMatrix = createPerspectiveMatrix(-fW, fW, -fH, fH, _cameraNearDist, _cameraFarDist); } else { viewMatrix = mx::Matrix44::createTranslation(mx::Vector3(0.0f, 0.0f, -ORTHO_VIEW_DISTANCE)); float fH = ORTHO_PROJECTION_HEIGHT; float fW = fH * aspectRatio; - projectionMatrix = mx::Camera::createOrthographicMatrix(-fW, fW, -fH, fH, 0.0f, ORTHO_VIEW_DISTANCE + _cameraFarDist); + projectionMatrix = createOrthographicMatrix(-fW, fW, -fH, fH, 0.0f, ORTHO_VIEW_DISTANCE + _cameraFarDist); } - +#ifdef MATERIALXVIEW_METAL_BACKEND + projectionMatrix[1][1] = -projectionMatrix[1][1]; + _colorTexture = metal_texture(); +#endif float turntableRotation = fmod((360.0f / _turntableSteps) * _turntableStep, 360.0f); float yRotation = _meshRotation[1] + (_turntableEnabled ? turntableRotation : 0.0f); mx::Matrix44 meshRotation = mx::Matrix44::createRotationZ(_meshRotation[2] / 180.0f * PI) * @@ -2461,7 +2328,7 @@ void Viewer::updateCameras() mx::Vector3 sphereCenter = (_geometryHandler->getMaximumBounds() + _geometryHandler->getMinimumBounds()) * 0.5; float r = (sphereCenter - _geometryHandler->getMinimumBounds()).getMagnitude(); _shadowCamera->setWorldMatrix(meshRotation * mx::Matrix44::createTranslation(-sphereCenter)); - _shadowCamera->setProjectionMatrix(mx::Camera::createOrthographicMatrix(-r, r, -r, r, 0.0f, r * 2.0f)); + _shadowCamera->setProjectionMatrix(mx::Camera::createOrthographicMatrixZP(-r, r, -r, r, 0.0f, r * 2.0f)); mx::ValuePtr value = dirLight->getInputValue("direction"); if (value->isA()) { @@ -2478,7 +2345,7 @@ void Viewer::updateDisplayedProperties() perform_layout(); } -mx::ImagePtr Viewer::getAmbientOcclusionImage(mx::GlslMaterialPtr material) +mx::ImagePtr Viewer::getAmbientOcclusionImage(mx::MaterialPtr material) { const mx::string AO_FILENAME_SUFFIX = "_ao"; const mx::string AO_FILENAME_EXTENSION = "png"; @@ -2517,14 +2384,14 @@ void Viewer::splitDirectLight(mx::ImagePtr envRadianceMap, mx::ImagePtr& indirec indirectMap = imagePair.first; } -mx::GlslMaterialPtr Viewer::getEnvironmentMaterial() +mx::MaterialPtr Viewer::getEnvironmentMaterial() { if (!_envMaterial) { mx::FilePath envFilename = _searchPath.find(mx::FilePath("resources/Lights/envmap_shader.mtlx")); try { - _envMaterial = mx::GlslMaterial::create(); + _envMaterial = _renderPipeline->createMaterial(); _envMaterial->generateEnvironmentShader(_genContext, envFilename, _stdLib, _envRadianceFilename); } catch (std::exception& e) @@ -2537,14 +2404,14 @@ mx::GlslMaterialPtr Viewer::getEnvironmentMaterial() return _envMaterial; } -mx::GlslMaterialPtr Viewer::getWireframeMaterial() +mx::MaterialPtr Viewer::getWireframeMaterial() { if (!_wireMaterial) { try { mx::ShaderPtr hwShader = mx::createConstantShader(_genContext, _stdLib, "__WIRE_SHADER__", mx::Color3(1.0f)); - _wireMaterial = mx::GlslMaterial::create(); + _wireMaterial = _renderPipeline->createMaterial(); _wireMaterial->generateShader(hwShader); } catch (std::exception& e) @@ -2557,96 +2424,6 @@ mx::GlslMaterialPtr Viewer::getWireframeMaterial() return _wireMaterial; } -mx::ImagePtr Viewer::getShadowMap() -{ - if (!_shadowMap) - { - // Generate shaders for shadow rendering. - if (!_shadowMaterial) - { - try - { - mx::ShaderPtr hwShader = mx::createDepthShader(_genContext, _stdLib, "__SHADOW_SHADER__"); - _shadowMaterial = mx::GlslMaterial::create(); - _shadowMaterial->generateShader(hwShader); - } - catch (std::exception& e) - { - std::cerr << "Failed to generate shadow shader: " << e.what() << std::endl; - _shadowMaterial = nullptr; - } - } - if (!_shadowBlurMaterial) - { - try - { - mx::ShaderPtr hwShader = mx::createBlurShader(_genContext, _stdLib, "__SHADOW_BLUR_SHADER__", "gaussian", 1.0f); - _shadowBlurMaterial = mx::GlslMaterial::create(); - _shadowBlurMaterial->generateShader(hwShader); - } - catch (std::exception& e) - { - std::cerr << "Failed to generate shadow blur shader: " << e.what() << std::endl; - _shadowBlurMaterial = nullptr; - } - } - - if (_shadowMaterial && _shadowBlurMaterial) - { - // Create framebuffer. - mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 2, mx::Image::BaseType::FLOAT); - framebuffer->bind(); - glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - // Render shadow geometry. - _shadowMaterial->bindShader(); - for (auto mesh : _geometryHandler->getMeshes()) - { - _shadowMaterial->bindMesh(mesh); - _shadowMaterial->bindViewInformation(_shadowCamera); - for (size_t i = 0; i < mesh->getPartitionCount(); i++) - { - mx::MeshPartitionPtr geom = mesh->getPartition(i); - _shadowMaterial->drawPartition(geom); - } - } - _shadowMap = framebuffer->getColorImage(); - - // Apply Gaussian blurring. - mx::ImageSamplingProperties blurSamplingProperties; - blurSamplingProperties.uaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; - blurSamplingProperties.vaddressMode = mx::ImageSamplingProperties::AddressMode::CLAMP; - blurSamplingProperties.filterType = mx::ImageSamplingProperties::FilterType::CLOSEST; - for (unsigned int i = 0; i < _shadowSoftness; i++) - { - framebuffer->bind(); - _shadowBlurMaterial->bindShader(); - if (_imageHandler->bindImage(_shadowMap, blurSamplingProperties)) - { - mx::GLTextureHandlerPtr textureHandler = std::static_pointer_cast(_imageHandler); - int textureLocation = textureHandler->getBoundTextureLocation(_shadowMap->getResourceId()); - if (textureLocation >= 0) - { - _shadowBlurMaterial->getProgram()->bindUniform("image_file", mx::Value::createValue(textureLocation)); - } - } - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - renderScreenSpaceQuad(_shadowBlurMaterial); - _imageHandler->releaseRenderResources(_shadowMap); - _shadowMap = framebuffer->getColorImage(); - } - - // Restore state for scene rendering. - glViewport(0, 0, m_fbsize[0], m_fbsize[1]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); - } - } - - return _shadowMap; -} - void Viewer::invalidateShadowMap() { if (_shadowMap) @@ -2656,63 +2433,6 @@ void Viewer::invalidateShadowMap() } } -void Viewer::updateAlbedoTable() -{ - if (_lightHandler->getAlbedoTable()) - { - return; - } - - // Create framebuffer. - mx::GLFramebufferPtr framebuffer = mx::GLFramebuffer::create(ALBEDO_TABLE_SIZE, ALBEDO_TABLE_SIZE, 3, mx::Image::BaseType::FLOAT); - framebuffer->bind(); - glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - // Create shader. - mx::ShaderPtr hwShader = mx::createAlbedoTableShader(_genContext, _stdLib, "__ALBEDO_TABLE_SHADER__"); - mx::GlslMaterialPtr material = mx::GlslMaterial::create(); - try - { - material->generateShader(hwShader); - } - catch (std::exception& e) - { - new ng::MessageDialog(this, ng::MessageDialog::Type::Warning, "Failed to generate albedo table shader", e.what()); - return; - } - - // Render albedo table. - material->bindShader(); - if (material->getProgram()->hasUniform(mx::HW::ALBEDO_TABLE_SIZE)) - { - material->getProgram()->bindUniform(mx::HW::ALBEDO_TABLE_SIZE, mx::Value::createValue(ALBEDO_TABLE_SIZE)); - } - renderScreenSpaceQuad(material); - - // Store albedo table image. - _imageHandler->releaseRenderResources(_lightHandler->getAlbedoTable()); - _lightHandler->setAlbedoTable(framebuffer->getColorImage()); - if (_saveGeneratedLights) - { - _imageHandler->saveImage("AlbedoTable.exr", _lightHandler->getAlbedoTable()); - } - - // Restore state for scene rendering. - glViewport(0, 0, m_fbsize[0], m_fbsize[1]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); -} - -void Viewer::renderScreenSpaceQuad(mx::GlslMaterialPtr material) -{ - if (!_quadMesh) - _quadMesh = mx::GeometryHandler::createQuadMesh(); - - material->bindMesh(_quadMesh); - material->drawPartition(_quadMesh->getPartition(0)); -} - void Viewer::toggleTurntable(bool enable) { _turntableEnabled = enable; @@ -2734,7 +2454,9 @@ void Viewer::toggleTurntable(bool enable) void Viewer::setShaderInterfaceType(mx::ShaderInterfaceType interfaceType) { _genContext.getOptions().shaderInterfaceType = interfaceType; +#ifndef MATERIALXVIEW_METAL_BACKEND _genContextEssl.getOptions().shaderInterfaceType = interfaceType; +#endif #if MATERIALX_BUILD_GEN_OSL _genContextOsl.getOptions().shaderInterfaceType = interfaceType; #endif diff --git a/source/MaterialXView/Viewer.h b/source/MaterialXView/Viewer.h index 3ba160c36e..3c56205d90 100644 --- a/source/MaterialXView/Viewer.h +++ b/source/MaterialXView/Viewer.h @@ -7,16 +7,15 @@ #define MATERIALXVIEW_VIEWER_H #include +#include -#include -#include - +#include #include #include #include +#include #include -#include #include @@ -33,6 +32,9 @@ class DocumentModifiers class Viewer : public ng::Screen { + friend class RenderPipeline; + friend class GLRenderPipeline; + friend class MetalRenderPipeline; public: Viewer(const std::string& materialFilename, const std::string& meshFilename, @@ -168,7 +170,7 @@ class Viewer : public ng::Screen } // Return the selected material. - mx::GlslMaterialPtr getSelectedMaterial() const + mx::MaterialPtr getSelectedMaterial() const { if (_selectedMaterial < _materials.size()) { @@ -200,7 +202,10 @@ class Viewer : public ng::Screen _exitRequested = true; } - void bakeTextures(); + void bakeTextures() + { + _renderPipeline->bakeTextures(); + } private: void draw_contents() override; @@ -208,7 +213,7 @@ class Viewer : public ng::Screen bool scroll_event(const ng::Vector2i& p, const ng::Vector2f& rel) override; bool mouse_motion_event(const ng::Vector2i& p, const ng::Vector2i& rel, int button, int modifiers) override; bool mouse_button_event(const ng::Vector2i& p, int button, bool down, int modifiers) override; - + void initContext(mx::GenContext& context); void loadMesh(const mx::FilePath& filename); void loadEnvironmentLight(); @@ -225,10 +230,10 @@ class Viewer : public ng::Screen // Assign the given material to the given geometry, or remove any // existing assignment if the given material is nullptr. - void assignMaterial(mx::MeshPartitionPtr geometry, mx::GlslMaterialPtr material); + void assignMaterial(mx::MeshPartitionPtr geometry, mx::MaterialPtr material); // Mark the given material as currently selected in the viewer. - void setSelectedMaterial(mx::GlslMaterialPtr material) + void setSelectedMaterial(mx::MaterialPtr material) { for (size_t i = 0; i < _materials.size(); i++) { @@ -261,23 +266,22 @@ class Viewer : public ng::Screen void createAdvancedSettings(Widget* parent); // Return the ambient occlusion image, if any, associated with the given material. - mx::ImagePtr getAmbientOcclusionImage(mx::GlslMaterialPtr material); + mx::ImagePtr getAmbientOcclusionImage(mx::MaterialPtr material); // Split the given radiance map into indirect and direct components, // returning a new indirect map and directional light document. void splitDirectLight(mx::ImagePtr envRadianceMap, mx::ImagePtr& indirectMap, mx::DocumentPtr& dirLightDoc); - mx::GlslMaterialPtr getEnvironmentMaterial(); - mx::GlslMaterialPtr getWireframeMaterial(); + mx::MaterialPtr getEnvironmentMaterial(); + mx::MaterialPtr getWireframeMaterial(); mx::ImagePtr getShadowMap(); void invalidateShadowMap(); - void renderFrame(); mx::ImagePtr getFrameImage(); mx::ImagePtr renderWedge(); void renderTurnable(); - void renderScreenSpaceQuad(mx::GlslMaterialPtr material); + void renderScreenSpaceQuad(mx::MaterialPtr material); // Update the directional albedo table. void updateAlbedoTable(); @@ -290,6 +294,7 @@ class Viewer : public ng::Screen private: ng::Window* _window; + RenderPipelinePtr _renderPipeline; mx::FilePath _materialFilename; mx::FileSearchPath _materialSearchPath; @@ -339,8 +344,8 @@ class Viewer : public ng::Screen bool _saveGeneratedLights; // Shadow mapping - mx::GlslMaterialPtr _shadowMaterial; - mx::GlslMaterialPtr _shadowBlurMaterial; + mx::MaterialPtr _shadowMaterial; + mx::MaterialPtr _shadowBlurMaterial; mx::ImagePtr _shadowMap; unsigned int _shadowSoftness; @@ -354,17 +359,18 @@ class Viewer : public ng::Screen ng::ComboBox* _geometrySelectionBox; // Material selections - std::vector _materials; - mx::GlslMaterialPtr _wireMaterial; + std::vector _materials; + mx::MaterialPtr _wireMaterial; size_t _selectedMaterial; ng::Label* _materialLabel; ng::ComboBox* _materialSelectionBox; PropertyEditor _propertyEditor; // Material assignments - std::map _materialAssignments; + std::map _materialAssignments; // Cameras + mx::CameraPtr _identityCamera; mx::CameraPtr _viewCamera; mx::CameraPtr _envCamera; mx::CameraPtr _shadowCamera; @@ -376,12 +382,13 @@ class Viewer : public ng::Screen // Supporting materials and geometry. mx::GeometryHandlerPtr _envGeometryHandler; - mx::GlslMaterialPtr _envMaterial; - mx::MeshPtr _quadMesh; + mx::MaterialPtr _envMaterial; // Shader generator contexts mx::GenContext _genContext; +#ifndef MATERIALXVIEW_METAL_BACKEND mx::GenContext _genContextEssl; +#endif #if MATERIALX_BUILD_GEN_OSL mx::GenContext _genContextOsl; #endif @@ -398,6 +405,9 @@ class Viewer : public ng::Screen // Render options bool _renderTransparency; bool _renderDoubleSided; + + // Framebuffer Color Texture + void* _colorTexture; // Scene options mx::StringVec _distanceUnitOptions; diff --git a/source/PyMaterialX/CMakeLists.txt b/source/PyMaterialX/CMakeLists.txt index c98da971ce..eda1ba0db4 100644 --- a/source/PyMaterialX/CMakeLists.txt +++ b/source/PyMaterialX/CMakeLists.txt @@ -49,6 +49,9 @@ if (MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_M if (MATERIALX_BUILD_GEN_GLSL) add_subdirectory(PyMaterialXGenGlsl) endif() + if (MATERIALX_BUILD_GEN_MSL) + add_subdirectory(PyMaterialXGenMsl) + endif() if (MATERIALX_BUILD_GEN_OSL) add_subdirectory(PyMaterialXGenOsl) endif() @@ -64,4 +67,7 @@ if (MATERIALX_BUILD_RENDER) if (MATERIALX_BUILD_GEN_OSL) add_subdirectory(PyMaterialXRenderOsl) endif() + if (APPLE AND MATERIALX_BUILD_GEN_MSL) + add_subdirectory(PyMaterialXRenderMsl) + endif() endif() diff --git a/source/PyMaterialX/PyMaterialXGenMsl/CMakeLists.txt b/source/PyMaterialX/PyMaterialXGenMsl/CMakeLists.txt new file mode 100644 index 0000000000..88e21687bc --- /dev/null +++ b/source/PyMaterialX/PyMaterialXGenMsl/CMakeLists.txt @@ -0,0 +1,28 @@ +file(GLOB pymaterialxgenmsl_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB pymaterialxgenmsl_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") + +pybind11_add_module(PyMaterialXGenMsl SHARED ${PYBIND11_MODULE_FLAGS} ${pymaterialxgenmsl_source} ${pymaterialxgenmsl_headers}) + +if(APPLE) + set_target_properties(PyMaterialXGenMsl PROPERTIES CXX_VISIBILITY_PRESET "default") +endif() + +set_target_properties( + PyMaterialXGenMsl + PROPERTIES + OUTPUT_NAME PyMaterialXGenMsl + COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}" + LINK_FLAGS "${EXTERNAL_LINK_FLAGS}" + INSTALL_RPATH "${MATERIALX_UP_TWO_RPATH}" + VERSION "${MATERIALX_LIBRARY_VERSION}" + SOVERSION "${MATERIALX_MAJOR_VERSION}" + DEBUG_POSTFIX "${MATERIALX_PYTHON_DEBUG_POSTFIX}") + +target_link_libraries( + PyMaterialXGenMsl + PUBLIC PyMaterialXGenShader + MaterialXGenMsl + PRIVATE ${CMAKE_DL_LIBS}) + +install(TARGETS PyMaterialXGenMsl + DESTINATION "python/MaterialX") diff --git a/source/PyMaterialX/PyMaterialXGenMsl/PyModule.cpp b/source/PyMaterialX/PyMaterialXGenMsl/PyModule.cpp new file mode 100644 index 0000000000..6eda44871b --- /dev/null +++ b/source/PyMaterialX/PyMaterialXGenMsl/PyModule.cpp @@ -0,0 +1,19 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +namespace py = pybind11; + +void bindPyMslShaderGenerator(py::module& mod); +void bindPyMslResourceBindingContext(py::module &mod); + +PYBIND11_MODULE(PyMaterialXGenMsl, mod) +{ + mod.doc() = "Module containing Python bindings for the MaterialXGenMsl library"; + + bindPyMslShaderGenerator(mod); + bindPyMslResourceBindingContext(mod); +} diff --git a/source/PyMaterialX/PyMaterialXGenMsl/PyMslShaderGenerator.cpp b/source/PyMaterialX/PyMaterialXGenMsl/PyMslShaderGenerator.cpp new file mode 100644 index 0000000000..870b9bc02c --- /dev/null +++ b/source/PyMaterialX/PyMaterialXGenMsl/PyMslShaderGenerator.cpp @@ -0,0 +1,37 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +// MSL shader generator bindings + +void bindPyMslShaderGenerator(py::module& mod) +{ + py::class_(mod, "MslShaderGenerator") + .def_static("create", &mx::MslShaderGenerator::create) + .def(py::init<>()) + .def("generate", &mx::MslShaderGenerator::generate) + .def("getTarget", &mx::MslShaderGenerator::getTarget) + .def("getVersion", &mx::MslShaderGenerator::getVersion); +} + +void bindPyMslResourceBindingContext(py::module &mod) +{ + py::class_(mod, "MslResourceBindingContext") + .def_static("create", &mx::MslResourceBindingContext::create) + .def(py::init()) + .def("emitDirectives", &mx::MslResourceBindingContext::emitDirectives) + .def("emitResourceBindings", &mx::MslResourceBindingContext::emitResourceBindings); +} diff --git a/source/PyMaterialX/PyMaterialXRenderGlsl/PyTextureBaker.cpp b/source/PyMaterialX/PyMaterialXRenderGlsl/PyTextureBaker.cpp index cff03138fc..ee4cb26081 100644 --- a/source/PyMaterialX/PyMaterialXRenderGlsl/PyTextureBaker.cpp +++ b/source/PyMaterialX/PyMaterialXRenderGlsl/PyTextureBaker.cpp @@ -13,34 +13,34 @@ namespace mx = MaterialX; void bindPyTextureBaker(py::module& mod) { - py::class_(mod, "TextureBaker") - .def_static("create", &mx::TextureBaker::create) - .def("setExtension", &mx::TextureBaker::setExtension) - .def("getExtension", &mx::TextureBaker::getExtension) - .def("setColorSpace", &mx::TextureBaker::setColorSpace) - .def("getColorSpace", &mx::TextureBaker::getColorSpace) - .def("setDistanceUnit", &mx::TextureBaker::setDistanceUnit) - .def("getDistanceUnit", &mx::TextureBaker::getDistanceUnit) - .def("setAverageImages", &mx::TextureBaker::setAverageImages) - .def("getAverageImages", &mx::TextureBaker::getAverageImages) - .def("setOptimizeConstants", &mx::TextureBaker::setOptimizeConstants) - .def("getOptimizeConstants", &mx::TextureBaker::getOptimizeConstants) - .def("setOutputImagePath", &mx::TextureBaker::setOutputImagePath) - .def("getOutputImagePath", &mx::TextureBaker::getOutputImagePath) - .def("setBakedGraphName", &mx::TextureBaker::setBakedGraphName) - .def("getBakedGraphName", &mx::TextureBaker::getBakedGraphName) - .def("setBakedGeomInfoName", &mx::TextureBaker::setBakedGeomInfoName) - .def("getBakedGeomInfoName", &mx::TextureBaker::getBakedGeomInfoName) - .def("setTextureFilenameTemplate", &mx::TextureBaker::setTextureFilenameTemplate) - .def("getTextureFilenameTemplate", &mx::TextureBaker::getTextureFilenameTemplate) - .def("setFilenameTemplateVarOverride", &mx::TextureBaker::setFilenameTemplateVarOverride) - .def("setHashImageNames", &mx::TextureBaker::setHashImageNames) - .def("getHashImageNames", &mx::TextureBaker::getHashImageNames) - .def("setTextureSpaceMin", &mx::TextureBaker::setTextureSpaceMin) - .def("getTextureSpaceMin", &mx::TextureBaker::getTextureSpaceMin) - .def("setTextureSpaceMax", &mx::TextureBaker::setTextureSpaceMax) - .def("getTextureSpaceMax", &mx::TextureBaker::getTextureSpaceMax) - .def("setupUnitSystem", &mx::TextureBaker::setupUnitSystem) - .def("bakeMaterialToDoc", &mx::TextureBaker::bakeMaterialToDoc) - .def("bakeAllMaterials", &mx::TextureBaker::bakeAllMaterials); + py::class_(mod, "TextureBaker") + .def_static("create", &mx::TextureBakerGlsl::create) + .def("setExtension", &mx::TextureBakerGlsl::setExtension) + .def("getExtension", &mx::TextureBakerGlsl::getExtension) + .def("setColorSpace", &mx::TextureBakerGlsl::setColorSpace) + .def("getColorSpace", &mx::TextureBakerGlsl::getColorSpace) + .def("setDistanceUnit", &mx::TextureBakerGlsl::setDistanceUnit) + .def("getDistanceUnit", &mx::TextureBakerGlsl::getDistanceUnit) + .def("setAverageImages", &mx::TextureBakerGlsl::setAverageImages) + .def("getAverageImages", &mx::TextureBakerGlsl::getAverageImages) + .def("setOptimizeConstants", &mx::TextureBakerGlsl::setOptimizeConstants) + .def("getOptimizeConstants", &mx::TextureBakerGlsl::getOptimizeConstants) + .def("setOutputImagePath", &mx::TextureBakerGlsl::setOutputImagePath) + .def("getOutputImagePath", &mx::TextureBakerGlsl::getOutputImagePath) + .def("setBakedGraphName", &mx::TextureBakerGlsl::setBakedGraphName) + .def("getBakedGraphName", &mx::TextureBakerGlsl::getBakedGraphName) + .def("setBakedGeomInfoName", &mx::TextureBakerGlsl::setBakedGeomInfoName) + .def("getBakedGeomInfoName", &mx::TextureBakerGlsl::getBakedGeomInfoName) + .def("setTextureFilenameTemplate", &mx::TextureBakerGlsl::setTextureFilenameTemplate) + .def("getTextureFilenameTemplate", &mx::TextureBakerGlsl::getTextureFilenameTemplate) + .def("setFilenameTemplateVarOverride", &mx::TextureBakerGlsl::setFilenameTemplateVarOverride) + .def("setHashImageNames", &mx::TextureBakerGlsl::setHashImageNames) + .def("getHashImageNames", &mx::TextureBakerGlsl::getHashImageNames) + .def("setTextureSpaceMin", &mx::TextureBakerGlsl::setTextureSpaceMin) + .def("getTextureSpaceMin", &mx::TextureBakerGlsl::getTextureSpaceMin) + .def("setTextureSpaceMax", &mx::TextureBakerGlsl::setTextureSpaceMax) + .def("getTextureSpaceMax", &mx::TextureBakerGlsl::getTextureSpaceMax) + .def("setupUnitSystem", &mx::TextureBakerGlsl::setupUnitSystem) + .def("bakeMaterialToDoc", &mx::TextureBakerGlsl::bakeMaterialToDoc) + .def("bakeAllMaterials", &mx::TextureBakerGlsl::bakeAllMaterials); } diff --git a/source/PyMaterialX/PyMaterialXRenderMsl/CMakeLists.txt b/source/PyMaterialX/PyMaterialXRenderMsl/CMakeLists.txt new file mode 100644 index 0000000000..ec562fe8cf --- /dev/null +++ b/source/PyMaterialX/PyMaterialXRenderMsl/CMakeLists.txt @@ -0,0 +1,30 @@ +file(GLOB pymaterialxrendermsl_source "${CMAKE_CURRENT_SOURCE_DIR}/*.mm") +file(GLOB pymaterialxrendermsl_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") + +pybind11_add_module(PyMaterialXRenderMsl SHARED ${PYBIND11_MODULE_FLAGS} ${pymaterialxrendermsl_source} ${pymaterialxrendermsl_headers}) + +if(APPLE) + set_target_properties(PyMaterialXRenderMsl PROPERTIES CXX_VISIBILITY_PRESET "default") +endif() + +set_target_properties( + PyMaterialXRenderMsl + PROPERTIES + OUTPUT_NAME PyMaterialXRenderMsl + COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}" + LINK_FLAGS "${EXTERNAL_LINK_FLAGS}" + INSTALL_RPATH "${MATERIALX_UP_TWO_RPATH}" + VERSION "${MATERIALX_LIBRARY_VERSION}" + SOVERSION "${MATERIALX_MAJOR_VERSION}" + DEBUG_POSTFIX "${MATERIALX_PYTHON_DEBUG_POSTFIX}") + +target_link_libraries( + PyMaterialXRenderMsl + PUBLIC PyMaterialXRender + MaterialXRenderMsl + PyMaterialXGenMsl + MaterialXGenMsl + PRIVATE ${CMAKE_DL_LIBS}) + +install(TARGETS PyMaterialXRenderMsl + DESTINATION "python/MaterialX") diff --git a/source/PyMaterialX/PyMaterialXRenderMsl/PyMetalTextureHandler.mm b/source/PyMaterialX/PyMaterialXRenderMsl/PyMetalTextureHandler.mm new file mode 100644 index 0000000000..495106be38 --- /dev/null +++ b/source/PyMaterialX/PyMaterialXRenderMsl/PyMetalTextureHandler.mm @@ -0,0 +1,22 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyMetalTextureHandler(py::module& mod) +{ + py::class_(mod, "MetalTextureHandler") + .def_static("create", &mx::MetalTextureHandler::create) + .def("bindImage", &mx::MetalTextureHandler::unbindImage) + .def("unbindImage", &mx::MetalTextureHandler::unbindImage) + .def("createRenderResources", &mx::MetalTextureHandler::createRenderResources) + .def("releaseRenderResources", &mx::MetalTextureHandler::releaseRenderResources, + py::arg("image") = nullptr); +} diff --git a/source/PyMaterialX/PyMaterialXRenderMsl/PyModule.mm b/source/PyMaterialX/PyMaterialXRenderMsl/PyModule.mm new file mode 100644 index 0000000000..10b440d21c --- /dev/null +++ b/source/PyMaterialX/PyMaterialXRenderMsl/PyModule.mm @@ -0,0 +1,23 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +namespace py = pybind11; + +void bindPyMslProgram(py::module& mod); +void bindPyMslRenderer(py::module& mod); +void bindPyMetalTextureHandler(py::module& mod); +void bindPyTextureBaker(py::module& mod); + +PYBIND11_MODULE(PyMaterialXRenderMsl, mod) +{ + mod.doc() = "Module containing Python bindings for the MaterialXRenderMsl library"; + + bindPyMslProgram(mod); + bindPyMslRenderer(mod); + bindPyMetalTextureHandler(mod); + bindPyTextureBaker(mod); +} diff --git a/source/PyMaterialX/PyMaterialXRenderMsl/PyMslProgram.mm b/source/PyMaterialX/PyMaterialXRenderMsl/PyMslProgram.mm new file mode 100644 index 0000000000..1339d242c0 --- /dev/null +++ b/source/PyMaterialX/PyMaterialXRenderMsl/PyMslProgram.mm @@ -0,0 +1,47 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyMslProgram(py::module& mod) +{ + py::class_(mod, "MslProgram") + .def_static("create", &mx::MslProgram::create) + .def("setStages", &mx::MslProgram::setStages) + .def("addStage", &mx::MslProgram::addStage) + .def("getStageSourceCode", &mx::MslProgram::getStageSourceCode) + .def("getShader", &mx::MslProgram::getShader) + .def("build", &mx::MslProgram::build) + .def("prepareUsedResources", &mx::MslProgram::prepareUsedResources) + .def("getUniformsList", &mx::MslProgram::getUniformsList) + .def("getAttributesList", &mx::MslProgram::getAttributesList) + .def("findInputs", &mx::MslProgram::findInputs) + .def("bind", &mx::MslProgram::bind) + .def("bindUniform", &mx::MslProgram::bindUniform) + .def("bindAttribute", &mx::MslProgram::bindAttribute) + .def("bindPartition", &mx::MslProgram::bindPartition) + .def("bindMesh", &mx::MslProgram::bindMesh) + .def("unbindGeometry", &mx::MslProgram::unbindGeometry) + .def("bindTextures", &mx::MslProgram::bindTextures) + .def("bindLighting", &mx::MslProgram::bindLighting) + .def("bindViewInformation", &mx::MslProgram::bindViewInformation) + .def("bindTimeAndFrame", &mx::MslProgram::bindTimeAndFrame, + py::arg("time") = 1.0f, py::arg("frame") = 1.0f); + + py::class_(mod, "Input") + .def_readwrite("location", &mx::MslProgram::Input::location) + .def_readwrite("size", &mx::MslProgram::Input::size) + .def_readwrite("typeString", &mx::MslProgram::Input::typeString) + .def_readwrite("value", &mx::MslProgram::Input::value) + .def_readwrite("isConstant", &mx::MslProgram::Input::isConstant) + .def_readwrite("path", &mx::MslProgram::Input::path) + .def(py::init()); +} diff --git a/source/PyMaterialX/PyMaterialXRenderMsl/PyMslRenderer.mm b/source/PyMaterialX/PyMaterialXRenderMsl/PyMslRenderer.mm new file mode 100644 index 0000000000..76150095c7 --- /dev/null +++ b/source/PyMaterialX/PyMaterialXRenderMsl/PyMslRenderer.mm @@ -0,0 +1,26 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyMslRenderer(py::module& mod) +{ + py::class_(mod, "MslRenderer") + .def_static("create", &mx::MslRenderer::create) + .def("initialize", &mx::MslRenderer::initialize) + .def("createProgram", static_cast(&mx::MslRenderer::createProgram)) + .def("createProgram", static_cast(&mx::MslRenderer::createProgram)) + .def("validateInputs", &mx::MslRenderer::validateInputs) + .def("render", &mx::MslRenderer::render) + .def("renderTextureSpace", &mx::MslRenderer::renderTextureSpace) + .def("captureImage", &mx::MslRenderer::captureImage) + .def("getProgram", &mx::MslRenderer::getProgram); +} diff --git a/source/PyMaterialX/PyMaterialXRenderMsl/PyTextureBaker.mm b/source/PyMaterialX/PyMaterialXRenderMsl/PyTextureBaker.mm new file mode 100644 index 0000000000..a75e053dad --- /dev/null +++ b/source/PyMaterialX/PyMaterialXRenderMsl/PyTextureBaker.mm @@ -0,0 +1,46 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyTextureBaker(py::module& mod) +{ + py::class_(mod, "TextureBaker") + .def_static("create", &mx::TextureBakerMsl::create) + .def("setExtension", &mx::TextureBakerMsl::setExtension) + .def("getExtension", &mx::TextureBakerMsl::getExtension) + .def("setColorSpace", &mx::TextureBakerMsl::setColorSpace) + .def("getColorSpace", &mx::TextureBakerMsl::getColorSpace) + .def("setDistanceUnit", &mx::TextureBakerMsl::setDistanceUnit) + .def("getDistanceUnit", &mx::TextureBakerMsl::getDistanceUnit) + .def("setAverageImages", &mx::TextureBakerMsl::setAverageImages) + .def("getAverageImages", &mx::TextureBakerMsl::getAverageImages) + .def("setOptimizeConstants", &mx::TextureBakerMsl::setOptimizeConstants) + .def("getOptimizeConstants", &mx::TextureBakerMsl::getOptimizeConstants) + .def("setOutputImagePath", &mx::TextureBakerMsl::setOutputImagePath) + .def("getOutputImagePath", &mx::TextureBakerMsl::getOutputImagePath) + .def("setBakedGraphName", &mx::TextureBakerMsl::setBakedGraphName) + .def("getBakedGraphName", &mx::TextureBakerMsl::getBakedGraphName) + .def("setBakedGeomInfoName", &mx::TextureBakerMsl::setBakedGeomInfoName) + .def("getBakedGeomInfoName", &mx::TextureBakerMsl::getBakedGeomInfoName) + .def("setTextureFilenameTemplate", &mx::TextureBakerMsl::setTextureFilenameTemplate) + .def("getTextureFilenameTemplate", &mx::TextureBakerMsl::getTextureFilenameTemplate) + .def("setFilenameTemplateVarOverride", &mx::TextureBakerMsl::setFilenameTemplateVarOverride) + .def("setHashImageNames", &mx::TextureBakerMsl::setHashImageNames) + .def("getHashImageNames", &mx::TextureBakerMsl::getHashImageNames) + .def("setTextureSpaceMin", &mx::TextureBakerMsl::setTextureSpaceMin) + .def("getTextureSpaceMin", &mx::TextureBakerMsl::getTextureSpaceMin) + .def("setTextureSpaceMax", &mx::TextureBakerMsl::setTextureSpaceMax) + .def("getTextureSpaceMax", &mx::TextureBakerMsl::getTextureSpaceMax) + .def("setupUnitSystem", &mx::TextureBakerMsl::setupUnitSystem) + .def("bakeMaterialToDoc", &mx::TextureBakerMsl::bakeMaterialToDoc) + .def("bakeAllMaterials", &mx::TextureBakerMsl::bakeAllMaterials); +}