diff --git a/documents/Specification/MaterialX.Specification.md b/documents/Specification/MaterialX.Specification.md index 65027694d8..187d6b299b 100644 --- a/documents/Specification/MaterialX.Specification.md +++ b/documents/Specification/MaterialX.Specification.md @@ -695,6 +695,17 @@ The type of the <image> node determines the number of channels output, which * `vaddressmode` (uniform string): determines how V coordinates outside the 0-1 range are processed before sampling the image; see below. Default is "periodic". * `filtertype` (uniform string): the type of texture filtering to use; standard values include "closest" (nearest-neighbor single-sample), "linear", and "cubic". If not specified, an application may use its own default texture filtering method. + + +* **`imagearray`**: samples data from an array of images, the image to be used is selected by index in to the array. The selected image is mapped on the geometry using the same process as described above for . + * `files` (uniform filenamearray): an array of URI of image files. + * `index` (integer): the index of the image to use from the array of images provided. If the value for `index` is outside the range of the provided image filenames, then the behavior is undefined. + * `default` (float or colorN or vectorN): a default value to use if the indexed filename from `files` can not be resolved (e.g. if the resolved file URI cannot be read). The `default` value must be the same type as the `` element itself. If `default` is not defined, the default color value will be 0.0 in all channels. + * `texcoord` (vector2): the name of a vector2-type node specifying the 2D texture coordinate at which the image data is read. Default is to use the current u,v coordinate. + * `uaddressmode` (uniform string): determines how U coordinates outside the 0-1 range are processed before sampling the image; see below. Default is "periodic". + * `vaddressmode` (uniform string): determines how V coordinates outside the 0-1 range are processed before sampling the image; see below. Default is "periodic". + * `filtertype` (uniform string): the type of texture filtering to use; standard values include "closest" (nearest-neighbor single-sample), "linear", and "cubic". If not specified, an application may use its own default texture filtering method. + * **`tiledimage`** (NG): samples data from a single image, with provisions for tiling and offsetting the image across uv space. diff --git a/libraries/stdlib/genglsl/mx_imagearray_color3.glsl b/libraries/stdlib/genglsl/mx_imagearray_color3.glsl new file mode 100644 index 0000000000..e0f9ae6e3c --- /dev/null +++ b/libraries/stdlib/genglsl/mx_imagearray_color3.glsl @@ -0,0 +1,7 @@ +#include "lib/$fileTransformUv" + +void mx_imagearray_color3(sampler2DArray tex_sampler, int index, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, vec2 uv_scale, vec2 uv_offset, out vec3 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv, index).rgb; +} diff --git a/libraries/stdlib/genglsl/mx_imagearray_color4.glsl b/libraries/stdlib/genglsl/mx_imagearray_color4.glsl new file mode 100644 index 0000000000..fb29accd1f --- /dev/null +++ b/libraries/stdlib/genglsl/mx_imagearray_color4.glsl @@ -0,0 +1,7 @@ +#include "lib/$fileTransformUv" + +void mx_imagearray_color4(sampler2DArray tex_sampler, int index, vec4 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, vec2 uv_scale, vec2 uv_offset, out vec4 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv, index); +} diff --git a/libraries/stdlib/genglsl/mx_imagearray_float.glsl b/libraries/stdlib/genglsl/mx_imagearray_float.glsl new file mode 100644 index 0000000000..f5805b2bc6 --- /dev/null +++ b/libraries/stdlib/genglsl/mx_imagearray_float.glsl @@ -0,0 +1,7 @@ +#include "lib/$fileTransformUv" + +void mx_imagearray_float(sampler2DArray tex_sampler, int index, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, vec2 uv_scale, vec2 uv_offset, out float result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv, index).r; +} diff --git a/libraries/stdlib/genglsl/mx_imagearray_vector2.glsl b/libraries/stdlib/genglsl/mx_imagearray_vector2.glsl new file mode 100644 index 0000000000..73666cecbf --- /dev/null +++ b/libraries/stdlib/genglsl/mx_imagearray_vector2.glsl @@ -0,0 +1,7 @@ +#include "lib/$fileTransformUv" + +void mx_imagearray_vector2(sampler2DArray tex_sampler, int index, vec2 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, vec2 uv_scale, vec2 uv_offset, out vec2 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv, index).rg; +} diff --git a/libraries/stdlib/genglsl/mx_imagearray_vector3.glsl b/libraries/stdlib/genglsl/mx_imagearray_vector3.glsl new file mode 100644 index 0000000000..3d0d81d241 --- /dev/null +++ b/libraries/stdlib/genglsl/mx_imagearray_vector3.glsl @@ -0,0 +1,7 @@ +#include "lib/$fileTransformUv" + +void mx_imagearray_vector3(sampler2DArray tex_sampler, int index, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, vec2 uv_scale, vec2 uv_offset, out vec3 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv, index).rgb; +} diff --git a/libraries/stdlib/genglsl/mx_imagearray_vector4.glsl b/libraries/stdlib/genglsl/mx_imagearray_vector4.glsl new file mode 100644 index 0000000000..aebc21b3ee --- /dev/null +++ b/libraries/stdlib/genglsl/mx_imagearray_vector4.glsl @@ -0,0 +1,7 @@ +#include "lib/$fileTransformUv" + +void mx_imagearray_vector4(sampler2DArray tex_sampler, int index, vec4 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, vec2 uv_scale, vec2 uv_offset, out vec4 result) +{ + vec2 uv = mx_transform_uv(texcoord, uv_scale, uv_offset); + result = texture(tex_sampler, uv, index); +} diff --git a/libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx b/libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx index ad3a21e9cf..98a098b59e 100644 --- a/libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx +++ b/libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx @@ -41,6 +41,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/stdlib/genmdl/stdlib_genmdl_impl.mtlx b/libraries/stdlib/genmdl/stdlib_genmdl_impl.mtlx index 6ac2b862dd..da226410f8 100644 --- a/libraries/stdlib/genmdl/stdlib_genmdl_impl.mtlx +++ b/libraries/stdlib/genmdl/stdlib_genmdl_impl.mtlx @@ -41,6 +41,8 @@ + + diff --git a/libraries/stdlib/genmsl/lib/mx_texture_array.metal b/libraries/stdlib/genmsl/lib/mx_texture_array.metal new file mode 100644 index 0000000000..c2d7e67a78 --- /dev/null +++ b/libraries/stdlib/genmsl/lib/mx_texture_array.metal @@ -0,0 +1,32 @@ +struct MetalTextureArray +{ + texture2d_array texArray; + sampler s; +}; + +float4 texture(MetalTextureArray mtlTex, float2 uv, int index) +{ + float4 ret = float4(0, 0, 1, 0); + + if (index >= -0.5 && !is_null_texture(mtlTex.texArray)) { + ret = vec4(mtlTex.texArray.sample(mtlTex.s, uv, index)); + } + + return ret; +} + +float4 textureLod(MetalTextureArray mtlTex, float2 uv, int index, float lod) +{ + float4 ret = float4(0, 0, 0, 0); + + if (index >= -0.5 && !is_null_texture(mtlTex.texArray)) { + ret = vec4(mtlTex.texArray.sample(mtlTex.s, uv, index, level(lod))); + } + + return ret; +} + +int2 textureSize(MetalTextureArray mtlTex, int mipLevel) +{ + return int2(mtlTex.texArray.get_width(), mtlTex.texArray.get_height()); +} diff --git a/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx b/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx index 3cb99052cf..0e7a078e66 100644 --- a/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx +++ b/libraries/stdlib/genmsl/stdlib_genmsl_impl.mtlx @@ -41,6 +41,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/stdlib/genosl/mx_elementat_filenamearray.osl b/libraries/stdlib/genosl/mx_elementat_filenamearray.osl new file mode 100644 index 0000000000..1b744bf65f --- /dev/null +++ b/libraries/stdlib/genosl/mx_elementat_filenamearray.osl @@ -0,0 +1,8 @@ +void mx_elementat_filenamearray(textureresources in, int index, output textureresource out) +{ + // we currently need the intermediate variable to use the list initializer syntax + // this was reported to OSL project in issue #1906 - if that gets fixed we may be able + // to revert to direct assignment to the output variable. + textureresource tmp = { in.filename[index], in.colorspace }; + out = tmp; +} diff --git a/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx b/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx index db318976c5..2c222ddd1f 100644 --- a/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx +++ b/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx @@ -41,6 +41,8 @@ + + diff --git a/libraries/stdlib/stdlib_defs.mtlx b/libraries/stdlib/stdlib_defs.mtlx index e6f8d431e7..23b912acd8 100644 --- a/libraries/stdlib/stdlib_defs.mtlx +++ b/libraries/stdlib/stdlib_defs.mtlx @@ -39,6 +39,7 @@ + @@ -202,6 +203,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/Materials/TestSuite/stdlib/channel/elementat.mtlx b/resources/Materials/TestSuite/stdlib/channel/elementat.mtlx new file mode 100644 index 0000000000..414693bc57 --- /dev/null +++ b/resources/Materials/TestSuite/stdlib/channel/elementat.mtlx @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/Materials/TestSuite/stdlib/texture/imagearray.mtlx b/resources/Materials/TestSuite/stdlib/texture/imagearray.mtlx new file mode 100644 index 0000000000..5130bb2d7f --- /dev/null +++ b/resources/Materials/TestSuite/stdlib/texture/imagearray.mtlx @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/MaterialXCore/Value.cpp b/source/MaterialXCore/Value.cpp index bb70aa04ae..c6730ec997 100644 --- a/source/MaterialXCore/Value.cpp +++ b/source/MaterialXCore/Value.cpp @@ -396,6 +396,29 @@ template class ValueRegistry ~ValueRegistry() { } }; +// +// Value alias registry class +// + +class ValueAliasRegistry +{ + public: + ValueAliasRegistry(string alias, string name) + { + // we're storing the creator function of the original type value type registered against the + // aliased name as well - this allows for things like "filename" and "filenamearray" to + // be correctly created as string or StringVec, instead of just falling through to the string + // default. + + auto it = Value::_creatorMap.find(name); + if (it != Value::_creatorMap.end()) + { + Value::_creatorMap[alias] = it->second; + } + } + ~ValueAliasRegistry() { } +}; + // // Template instantiations // @@ -411,6 +434,9 @@ template class ValueRegistry template MX_CORE_API T fromValueString(const string& value); \ ValueRegistry registry##T; +#define ALIAS_TYPE(alias, name) \ + ValueAliasRegistry registry##alias(#alias, name); + // Base types INSTANTIATE_TYPE(int, "integer") INSTANTIATE_TYPE(bool, "boolean") @@ -423,12 +449,14 @@ INSTANTIATE_TYPE(Vector4, "vector4") INSTANTIATE_TYPE(Matrix33, "matrix33") INSTANTIATE_TYPE(Matrix44, "matrix44") INSTANTIATE_TYPE(string, "string") +ALIAS_TYPE(filename, "string") // Array types INSTANTIATE_TYPE(IntVec, "integerarray") INSTANTIATE_TYPE(BoolVec, "booleanarray") INSTANTIATE_TYPE(FloatVec, "floatarray") INSTANTIATE_TYPE(StringVec, "stringarray") +ALIAS_TYPE(filenamearray, "stringarray") // Alias types INSTANTIATE_TYPE(long, "integer") diff --git a/source/MaterialXCore/Value.h b/source/MaterialXCore/Value.h index 4dae4ab135..55d1fcb12c 100644 --- a/source/MaterialXCore/Value.h +++ b/source/MaterialXCore/Value.h @@ -124,6 +124,7 @@ class MX_CORE_API Value protected: template friend class ValueRegistry; + friend class ValueAliasRegistry; using CreatorFunction = ValuePtr (*)(const string&); using CreatorMap = std::unordered_map; diff --git a/source/MaterialXGenGlsl/GlslResourceBindingContext.cpp b/source/MaterialXGenGlsl/GlslResourceBindingContext.cpp index d29ee65851..de7a93dabb 100644 --- a/source/MaterialXGenGlsl/GlslResourceBindingContext.cpp +++ b/source/MaterialXGenGlsl/GlslResourceBindingContext.cpp @@ -65,7 +65,7 @@ void GlslResourceBindingContext::emitResourceBindings(GenContext& context, const bool hasValueUniforms = false; for (auto uniform : uniforms.getVariableOrder()) { - if (uniform->getType() != Type::FILENAME) + if (!uniform->getType().isFilename()) { hasValueUniforms = true; break; @@ -79,7 +79,7 @@ void GlslResourceBindingContext::emitResourceBindings(GenContext& context, const generator.emitScopeBegin(stage); for (auto uniform : uniforms.getVariableOrder()) { - if (uniform->getType() != Type::FILENAME) + if (!uniform->getType().isFilename()) { generator.emitLineBegin(stage); generator.emitVariableDeclaration(uniform, EMPTY_STRING, context, stage, false); @@ -93,7 +93,7 @@ void GlslResourceBindingContext::emitResourceBindings(GenContext& context, const // Second, emit all sampler uniforms as separate uniforms with separate layout bindings for (auto uniform : uniforms.getVariableOrder()) { - if (uniform->getType() == Type::FILENAME) + if (uniform->getType().isFilename()) { generator.emitString("layout (binding=" + std::to_string(_separateBindingLocation ? _hwUniformBindLocation++ : _hwSamplerBindLocation++) + ") " + syntax.getUniformQualifier() + " ", stage); generator.emitVariableDeclaration(uniform, EMPTY_STRING, context, stage, false); diff --git a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp index c648441c7e..f89c551ff9 100644 --- a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp +++ b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp @@ -135,6 +135,12 @@ GlslShaderGenerator::GlslShaderGenerator() : "IM_image_vector2_" + GlslShaderGenerator::TARGET, "IM_image_vector3_" + GlslShaderGenerator::TARGET, "IM_image_vector4_" + GlslShaderGenerator::TARGET, + "IM_imagearray_float_" + GlslShaderGenerator::TARGET, + "IM_imagearray_color3_" + GlslShaderGenerator::TARGET, + "IM_imagearray_color4_" + GlslShaderGenerator::TARGET, + "IM_imagearray_vector2_" + GlslShaderGenerator::TARGET, + "IM_imagearray_vector3_" + GlslShaderGenerator::TARGET, + "IM_imagearray_vector4_" + GlslShaderGenerator::TARGET, }; registerImplementation(elementNames, HwImageNode::create); @@ -699,6 +705,12 @@ void GlslShaderGenerator::emitVariableDeclaration(const ShaderPort* variable, co string str = qualifier.empty() ? EMPTY_STRING : qualifier + " "; emitString(str + "sampler2D " + variable->getVariable(), stage); } + else if (variable->getType() == Type::FILENAMEARRAY) + { + // Samplers must always be uniforms + string str = qualifier.empty() ? EMPTY_STRING : qualifier + " "; + emitString(str + "sampler2DArray " + variable->getVariable(), stage); + } else { string str = qualifier.empty() ? EMPTY_STRING : qualifier + " "; diff --git a/source/MaterialXGenGlsl/GlslSyntax.cpp b/source/MaterialXGenGlsl/GlslSyntax.cpp index 651a4ab5ff..f9c2312552 100644 --- a/source/MaterialXGenGlsl/GlslSyntax.cpp +++ b/source/MaterialXGenGlsl/GlslSyntax.cpp @@ -80,6 +80,22 @@ class GlslIntegerArrayTypeSyntax : public GlslArrayTypeSyntax } }; +class GlslFilenameArrayTypeSyntax : public GlslArrayTypeSyntax +{ + public: + explicit GlslFilenameArrayTypeSyntax(const string& name) : + GlslArrayTypeSyntax(name) + { + } + + protected: + size_t getSize(const Value& value) const override + { + vector valueArray = value.asA>(); + return valueArray.size(); + } +}; + } // anonymous namespace const string GlslSyntax::INPUT_QUALIFIER = "in"; @@ -260,6 +276,11 @@ GlslSyntax::GlslSyntax() EMPTY_STRING, EMPTY_STRING)); + registerTypeSyntax( + Type::FILENAMEARRAY, + std::make_shared( + "sampler2DArray")); + registerTypeSyntax( Type::BSDF, std::make_shared( diff --git a/source/MaterialXGenMsl/MslResourceBindingContext.cpp b/source/MaterialXGenMsl/MslResourceBindingContext.cpp index 297d907418..6245a06ac6 100644 --- a/source/MaterialXGenMsl/MslResourceBindingContext.cpp +++ b/source/MaterialXGenMsl/MslResourceBindingContext.cpp @@ -38,7 +38,7 @@ void MslResourceBindingContext::emitResourceBindings(GenContext& context, const bool hasValueUniforms = false; for (auto uniform : uniforms.getVariableOrder()) { - if (uniform->getType() != Type::FILENAME) + if (!uniform->getType().isFilename()) { hasValueUniforms = true; break; @@ -51,7 +51,7 @@ void MslResourceBindingContext::emitResourceBindings(GenContext& context, const generator.emitScopeBegin(stage); for (auto uniform : uniforms.getVariableOrder()) { - if (uniform->getType() != Type::FILENAME) + if (!uniform->getType().isFilename()) { generator.emitLineBegin(stage); generator.emitVariableDeclaration(uniform, EMPTY_STRING, context, stage, false); diff --git a/source/MaterialXGenMsl/MslShaderGenerator.cpp b/source/MaterialXGenMsl/MslShaderGenerator.cpp index 9511fac7ba..663213c701 100644 --- a/source/MaterialXGenMsl/MslShaderGenerator.cpp +++ b/source/MaterialXGenMsl/MslShaderGenerator.cpp @@ -139,6 +139,12 @@ MslShaderGenerator::MslShaderGenerator() : "IM_image_vector2_" + MslShaderGenerator::TARGET, "IM_image_vector3_" + MslShaderGenerator::TARGET, "IM_image_vector4_" + MslShaderGenerator::TARGET, + "IM_imagearray_float_" + MslShaderGenerator::TARGET, + "IM_imagearray_color3_" + MslShaderGenerator::TARGET, + "IM_imagearray_color4_" + MslShaderGenerator::TARGET, + "IM_imagearray_vector2_" + MslShaderGenerator::TARGET, + "IM_imagearray_vector3_" + MslShaderGenerator::TARGET, + "IM_imagearray_vector4_" + MslShaderGenerator::TARGET, }; registerImplementation(elementNames, HwImageNode::create); @@ -269,6 +275,7 @@ void MslShaderGenerator::MetalizeGeneratedShader(ShaderStage& shaderStage) const // Renames GLSL constructs that are used in shared code to MSL equivalent constructs. std::unordered_map replaceTokens; replaceTokens["sampler2D"] = "MetalTexture"; + replaceTokens["sampler2DArray"] = "MetalTextureArray"; replaceTokens["dFdy"] = "dfdy"; replaceTokens["dFdx"] = "dfdx"; @@ -456,7 +463,7 @@ void MslShaderGenerator::emitGlobalVariables(GenContext& context, { for (size_t i = 0; i < uniforms.size(); ++i) { - if (uniforms[i]->getType() != Type::FILENAME) + if (!uniforms[i]->getType().isFilename()) { emitLineBegin(stage); emitString(separator, stage); @@ -474,7 +481,7 @@ void MslShaderGenerator::emitGlobalVariables(GenContext& context, } emitLineEnd(stage, false); } - else + else if (uniforms[i]->getType() == Type::FILENAME) { if (globalContextInit) { @@ -498,6 +505,30 @@ void MslShaderGenerator::emitGlobalVariables(GenContext& context, emitLine(uniforms[i]->getVariable() + "(" + uniforms[i]->getVariable() + ")", stage, false); } } + else if (uniforms[i]->getType() == Type::FILENAMEARRAY) + { + if (globalContextInit) + { + emitString(separator, stage); + emitString("MetalTextureArray ", stage); + emitScopeBegin(stage); + emitString(TEXTUREARRAY_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); + emitString(uniforms[i]->getVariable() + "(" + uniforms[i]->getVariable() + ")", stage); + } + } if (globalContextInit || globalContextConstructorParams || globalContextConstructorInit) separator = ", "; @@ -522,6 +553,15 @@ void MslShaderGenerator::emitGlobalVariables(GenContext& context, emitString(" [[sampler(" + std::to_string(tex_slot++) + ")]]", stage); emitLineEnd(stage, false); } + else if (uniform->getType() == Type::FILENAMEARRAY) + { + emitString(separator, stage); + emitString("texture2d_array " + TEXTUREARRAY_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; @@ -740,6 +780,7 @@ void MslShaderGenerator::emitConstantBufferDeclarations(GenContext& context, void MslShaderGenerator::emitMetalTextureClass(GenContext& context, ShaderStage& stage) const { emitLibraryInclude("stdlib/genmsl/lib/mx_texture.metal", context, stage); + emitLibraryInclude("stdlib/genmsl/lib/mx_texture_array.metal", context, stage); } void MslShaderGenerator::emitLightData(GenContext& context, ShaderStage& stage) const @@ -1211,6 +1252,12 @@ void MslShaderGenerator::emitVariableDeclaration(const ShaderPort* variable, con string str = qualifier.empty() ? EMPTY_STRING : qualifier + " "; emitString(str + "MetalTexture " + variable->getVariable(), stage); } + else if (variable->getType() == Type::FILENAMEARRAY) + { + // Samplers must always be uniforms + string str = qualifier.empty() ? EMPTY_STRING : qualifier + " "; + emitString(str + "MetalTextureArray " + variable->getVariable(), stage); + } else { string str = qualifier.empty() ? EMPTY_STRING : qualifier + " "; diff --git a/source/MaterialXGenMsl/MslShaderGenerator.h b/source/MaterialXGenMsl/MslShaderGenerator.h index 6796f545d3..dff4de9d54 100644 --- a/source/MaterialXGenMsl/MslShaderGenerator.h +++ b/source/MaterialXGenMsl/MslShaderGenerator.h @@ -14,6 +14,7 @@ #include #define TEXTURE_NAME(t) ((t) + "_tex") +#define TEXTUREARRAY_NAME(t) ((t) + "_texArray") #define SAMPLER_NAME(t) ((t) + "_sampler") MATERIALX_NAMESPACE_BEGIN diff --git a/source/MaterialXGenMsl/MslSyntax.cpp b/source/MaterialXGenMsl/MslSyntax.cpp index 30f44c5d1c..1d9b8c354c 100644 --- a/source/MaterialXGenMsl/MslSyntax.cpp +++ b/source/MaterialXGenMsl/MslSyntax.cpp @@ -80,6 +80,22 @@ class MslIntegerArrayTypeSyntax : public MslArrayTypeSyntax } }; +class MslFilenameArrayTypeSyntax : public MslArrayTypeSyntax +{ + public: + explicit MslFilenameArrayTypeSyntax(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"; @@ -242,6 +258,11 @@ MslSyntax::MslSyntax() EMPTY_STRING, EMPTY_STRING)); + registerTypeSyntax( + Type::FILENAMEARRAY, + std::make_shared( + "MetalTextureArray")); + registerTypeSyntax( Type::BSDF, std::make_shared( diff --git a/source/MaterialXGenOsl/OslSyntax.cpp b/source/MaterialXGenOsl/OslSyntax.cpp index a85821c4fd..1b1f2f17fc 100644 --- a/source/MaterialXGenOsl/OslSyntax.cpp +++ b/source/MaterialXGenOsl/OslSyntax.cpp @@ -221,6 +221,37 @@ class OSLFilenameTypeSyntax : public AggregateTypeSyntax } }; +class OSLFilenameArrayTypeSyntax : public AggregateTypeSyntax +{ + public: + OSLFilenameArrayTypeSyntax(const string& name, const string& defaultValue, const string& uniformDefaultValue, + const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, + const StringVec& members = EMPTY_MEMBERS) : + AggregateTypeSyntax(name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members) + { + } + + string getValue(const ShaderPort* port, bool uniform) const override + { + if (!port) + { + return EMPTY_STRING; + } + + const string prefix = uniform ? "{" : getName() + "("; + const string suffix = uniform ? "}" : ")"; + const string filename = port->getValue() ? port->getValue()->getValueString() : EMPTY_STRING; + return prefix + "{\"" + filename + "\"}, \"" + port->getColorSpace() + "\"" + suffix; + } + + string getValue(const Value& value, bool uniform) const override + { + const string prefix = uniform ? "{" : getName() + "("; + const string suffix = uniform ? "}" : ")"; + return prefix + "{\"" + value.getValueString() + "\"}, \"\"" + suffix; + } +}; + } // anonymous namespace const string OslSyntax::OUTPUT_QUALIFIER = "output"; @@ -253,7 +284,7 @@ OslSyntax::OslSyntax() "isinf", "isfinite", "erf", "erfc", "cross", "dot", "length", "distance", "normalize", "faceforward", "reflect", "fresnel", "transform", "transformu", "rotate", "luminance", "blackbody", "wavelength_color", "transformc", "determinant", "transpose", "step", "smoothstep", "linearstep", "smooth_linearstep", "aastep", - "hash", "strlen", "getchar", "startswith", "endswith", "substr", "stof", "stoi", "concat", "textureresource", + "hash", "strlen", "getchar", "startswith", "endswith", "substr", "stof", "stoi", "concat", "textureresource", "textureresources", "backfacing", "raytype", "iscameraray", "isdiffuseray", "isglossyray", "isshadowray", "getmatrix", "emission", "background", "diffuse", "oren_nayer", "translucent", "phong", "ward", "microfacet", "reflection", "transparent", "debug", "holdout", "subsurface", "sheen", @@ -367,10 +398,19 @@ OslSyntax::OslSyntax() std::make_shared( "textureresource ", "textureresource (\"\", \"\")", - "(\"\", \"\")", + "{\"\", \"\"}", EMPTY_STRING, "struct textureresource { string filename; string colorspace; };")); + registerTypeSyntax( + Type::FILENAMEARRAY, + std::make_shared( + "textureresources ", + "textureresources ({}, \"\")", + "({}, \"\")", + EMPTY_STRING, + "struct textureresources { string filename[100]; string colorspace; };")); + registerTypeSyntax( Type::BSDF, std::make_shared( diff --git a/source/MaterialXGenShader/HwShaderGenerator.cpp b/source/MaterialXGenShader/HwShaderGenerator.cpp index 20f91d6985..3ac6dd7e74 100644 --- a/source/MaterialXGenShader/HwShaderGenerator.cpp +++ b/source/MaterialXGenShader/HwShaderGenerator.cpp @@ -462,10 +462,10 @@ ShaderPtr HwShaderGenerator::createShader(const string& name, ElementPtr element { for (ShaderInput* input : node->getInputs()) { - if (!input->getConnection() && input->getType() == Type::FILENAME) + if (!input->getConnection() && input->getType().isFilename()) { // Create the uniform using the filename type to make this uniform into a texture sampler. - ShaderPort* filename = psPublicUniforms->add(Type::FILENAME, input->getVariable(), input->getValue()); + ShaderPort* filename = psPublicUniforms->add(input->getType(), input->getVariable(), input->getValue()); filename->setPath(input->getPath()); // Assing the uniform name to the input value diff --git a/source/MaterialXGenShader/TypeDesc.cpp b/source/MaterialXGenShader/TypeDesc.cpp index 5d51680211..121912044d 100644 --- a/source/MaterialXGenShader/TypeDesc.cpp +++ b/source/MaterialXGenShader/TypeDesc.cpp @@ -130,6 +130,7 @@ TYPEDESC_REGISTER_TYPE(MATRIX33, "matrix33") TYPEDESC_REGISTER_TYPE(MATRIX44, "matrix44") TYPEDESC_REGISTER_TYPE(STRING, "string") TYPEDESC_REGISTER_TYPE(FILENAME, "filename") +TYPEDESC_REGISTER_TYPE(FILENAMEARRAY, "filenamearray") TYPEDESC_REGISTER_TYPE(BSDF, "BSDF") TYPEDESC_REGISTER_TYPE(EDF, "EDF") TYPEDESC_REGISTER_TYPE(VDF, "VDF") diff --git a/source/MaterialXGenShader/TypeDesc.h b/source/MaterialXGenShader/TypeDesc.h index 95d2851915..0159b273d4 100644 --- a/source/MaterialXGenShader/TypeDesc.h +++ b/source/MaterialXGenShader/TypeDesc.h @@ -120,6 +120,8 @@ class MX_GENSHADER_API TypeDesc /// Return true if the type represents a closure. bool isClosure() const { return (_semantic == SEMANTIC_CLOSURE || _semantic == SEMANTIC_SHADER || _semantic == SEMANTIC_MATERIAL); } + bool isFilename() const { return _semantic == SEMANTIC_FILENAME; } + /// Return true if the type represents a struct. bool isStruct() const { return _basetype == BASETYPE_STRUCT; } @@ -218,6 +220,7 @@ TYPEDESC_DEFINE_TYPE(MATRIX33, "matrix33", TypeDesc::BASETYPE_FLOAT, TypeDesc::S TYPEDESC_DEFINE_TYPE(MATRIX44, "matrix44", TypeDesc::BASETYPE_FLOAT, TypeDesc::SEMANTIC_MATRIX, 16) TYPEDESC_DEFINE_TYPE(STRING, "string", TypeDesc::BASETYPE_STRING, TypeDesc::SEMANTIC_NONE, 1) TYPEDESC_DEFINE_TYPE(FILENAME, "filename", TypeDesc::BASETYPE_STRING, TypeDesc::SEMANTIC_FILENAME, 1) +TYPEDESC_DEFINE_TYPE(FILENAMEARRAY, "filenamearray", TypeDesc::BASETYPE_STRING, TypeDesc::SEMANTIC_FILENAME, 0) TYPEDESC_DEFINE_TYPE(BSDF, "BSDF", TypeDesc::BASETYPE_NONE, TypeDesc::SEMANTIC_CLOSURE, 1) TYPEDESC_DEFINE_TYPE(EDF, "EDF", TypeDesc::BASETYPE_NONE, TypeDesc::SEMANTIC_CLOSURE, 1) TYPEDESC_DEFINE_TYPE(VDF, "VDF", TypeDesc::BASETYPE_NONE, TypeDesc::SEMANTIC_CLOSURE, 1) diff --git a/source/MaterialXRender/ImageHandler.cpp b/source/MaterialXRender/ImageHandler.cpp index 71721c4775..d103ee42bb 100644 --- a/source/MaterialXRender/ImageHandler.cpp +++ b/source/MaterialXRender/ImageHandler.cpp @@ -147,7 +147,7 @@ ImagePtr ImageHandler::acquireImage(const FilePath& filePath, const Color4& defa return defaultImage; } -bool ImageHandler::bindImage(ImagePtr, const ImageSamplingProperties&) +bool ImageHandler::bindImage(ImagePtr, const ImageSamplingProperties&, vector additionalImages) { return false; } @@ -165,7 +165,7 @@ void ImageHandler::unbindImages() } } -bool ImageHandler::createRenderResources(ImagePtr, bool, bool) +bool ImageHandler::createRenderResources(ImagePtr, bool, bool, vector) { return false; } diff --git a/source/MaterialXRender/ImageHandler.h b/source/MaterialXRender/ImageHandler.h index e5d2082f8d..2fc5c9e112 100644 --- a/source/MaterialXRender/ImageHandler.h +++ b/source/MaterialXRender/ImageHandler.h @@ -196,7 +196,7 @@ class MX_RENDER_API ImageHandler /// Bind an image for rendering. /// @param image The image to bind. /// @param samplingProperties Sampling properties for the image. - virtual bool bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties); + virtual bool bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties, vector additionalImages = {}); /// Unbind an image, making it no longer active for rendering. /// @param image The image to unbind. @@ -230,7 +230,7 @@ class MX_RENDER_API ImageHandler } /// Create rendering resources for the given image. - virtual bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false); + virtual bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false, vector additionalImages = {}); /// Release rendering resources for the given image, or for all cached images /// if no image pointer is specified. diff --git a/source/MaterialXRenderGlsl/GLTextureHandler.cpp b/source/MaterialXRenderGlsl/GLTextureHandler.cpp index 47536243cf..e458ebd845 100644 --- a/source/MaterialXRenderGlsl/GLTextureHandler.cpp +++ b/source/MaterialXRenderGlsl/GLTextureHandler.cpp @@ -27,7 +27,7 @@ GLTextureHandler::GLTextureHandler(ImageLoaderPtr imageLoader) : _boundTextureLocations.resize(maxTextureUnits, GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID); } -bool GLTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties) +bool GLTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties, vector additionalImages) { if (!image) { @@ -37,7 +37,7 @@ bool GLTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& // Create renderer resources if needed. if (image->getResourceId() == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { - if (!createRenderResources(image, true)) + if (!createRenderResources(image, true, false, additionalImages)) { return false; } @@ -56,8 +56,14 @@ bool GLTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& } _boundTextureLocations[textureUnit] = image->getResourceId(); + auto textureTarget = GL_TEXTURE_2D; + if (!additionalImages.empty()) + { + textureTarget = GL_TEXTURE_2D_ARRAY; + } + glActiveTexture(GL_TEXTURE0 + textureUnit); - glBindTexture(GL_TEXTURE_2D, image->getResourceId()); + glBindTexture(textureTarget, image->getResourceId()); // Set up texture properties GLint minFilterType = mapFilterTypeToGL(samplingProperties.filterType, samplingProperties.enableMipmaps); @@ -65,12 +71,12 @@ bool GLTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& GLint uaddressMode = mapAddressModeToGL(samplingProperties.uaddressMode); GLint vaddressMode = mapAddressModeToGL(samplingProperties.vaddressMode); Color4 borderColor(samplingProperties.defaultColor); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, uaddressMode); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vaddressMode); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilterType); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilterType); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, 16.0f); + glTexParameterfv(textureTarget, GL_TEXTURE_BORDER_COLOR, borderColor.data()); + glTexParameteri(textureTarget, GL_TEXTURE_WRAP_S, uaddressMode); + glTexParameteri(textureTarget, GL_TEXTURE_WRAP_T, vaddressMode); + glTexParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, minFilterType); + glTexParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, magFilterType); + glTexParameterf(textureTarget, GL_TEXTURE_MAX_ANISOTROPY, 16.0f); return true; } @@ -91,8 +97,19 @@ bool GLTextureHandler::unbindImage(ImagePtr image) return false; } -bool GLTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool) +bool GLTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool, vector additionalImages) { + for (const auto& additionalImage : additionalImages) + { + if ((image->getWidth() != additionalImage->getWidth()) || + (image->getHeight() != additionalImage->getHeight()) || + (image->getBaseType() != additionalImage->getBaseType()) || + (image->getChannelCount() != additionalImage->getChannelCount())) + { + throw Exception("All images in a Texture Array must have the same texture geometry."); + } + } + if (image->getResourceId() == GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID) { unsigned int resourceId; @@ -111,26 +128,50 @@ bool GLTextureHandler::createRenderResources(ImagePtr image, bool generateMipMap return false; } + auto textureTarget = GL_TEXTURE_2D; + if (!additionalImages.empty()) + { + textureTarget = GL_TEXTURE_2D_ARRAY; + } + glActiveTexture(GL_TEXTURE0 + textureUnit); - glBindTexture(GL_TEXTURE_2D, image->getResourceId()); + glBindTexture(textureTarget, image->getResourceId()); int glType, glFormat, glInternalFormat; mapTextureFormatToGL(image->getBaseType(), image->getChannelCount(), false, glType, glFormat, glInternalFormat); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, image->getWidth(), image->getHeight(), - 0, glFormat, glType, image->getResourceBuffer()); + + if (textureTarget == GL_TEXTURE_2D) + { + glTexImage2D(textureTarget, 0, glInternalFormat, image->getWidth(), image->getHeight(), + 0, glFormat, glType, image->getResourceBuffer()); + } + else if (textureTarget == GL_TEXTURE_2D_ARRAY) + { + int i = 0; + glTexImage3D(textureTarget, 0, glInternalFormat, image->getWidth(), image->getHeight(), + (GLsizei) (additionalImages.size() + 1), 0, glFormat, glType, NULL); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i++, image->getWidth(), image->getHeight(), + 1, glFormat, glType, image->getResourceBuffer()); + for (const auto& additionalImage : additionalImages) + { + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i++, additionalImage->getWidth(), additionalImage->getHeight(), + 1, glFormat, glType, additionalImage->getResourceBuffer()); + } + } + if (image->getChannelCount() == 1) { GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + glTexParameteriv(textureTarget, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } if (generateMipMaps) { - glGenerateMipmap(GL_TEXTURE_2D); + glGenerateMipmap(textureTarget); } - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(textureTarget, 0); return true; } diff --git a/source/MaterialXRenderGlsl/GLTextureHandler.h b/source/MaterialXRenderGlsl/GLTextureHandler.h index 13eaab098f..7b35efe0b3 100644 --- a/source/MaterialXRenderGlsl/GLTextureHandler.h +++ b/source/MaterialXRenderGlsl/GLTextureHandler.h @@ -31,13 +31,13 @@ class MX_RENDERGLSL_API GLTextureHandler : public ImageHandler /// 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(ImagePtr image, const ImageSamplingProperties& samplingProperties) override; + bool bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties, vector additionalImages = {}) override; /// Unbind an image. bool unbindImage(ImagePtr image) override; /// Create rendering resources for the given image. - bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false) override; + bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false, vector additionalImages = {}) override; /// Release rendering resources for the given image, or for all cached images /// if no image pointer is specified. diff --git a/source/MaterialXRenderGlsl/GlslMaterial.cpp b/source/MaterialXRenderGlsl/GlslMaterial.cpp index 064ab898ff..40ccf5cb7b 100644 --- a/source/MaterialXRenderGlsl/GlslMaterial.cpp +++ b/source/MaterialXRenderGlsl/GlslMaterial.cpp @@ -166,16 +166,11 @@ void GlslMaterial::bindImages(ImageHandlerPtr imageHandler, const FileSearchPath } for (const auto& uniform : publicUniforms->getVariableOrder()) { - if (uniform->getType() != Type::FILENAME) + if (!uniform->getType().isFilename()) { 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; @@ -184,7 +179,26 @@ void GlslMaterial::bindImages(ImageHandlerPtr imageHandler, const FileSearchPath // Set the requested mipmap sampling property, samplingProperties.enableMipmaps = enableMipmaps; - ImagePtr image = bindImage(filename, uniformVariable, imageHandler, samplingProperties); + ImagePtr image = nullptr; + if (uniform->getType() == Type::FILENAME) + { + std::string filename = uniform->getValue() ? searchPath.find(uniform->getValue()->getValueString()) : ""; + image = bindImage(filename, uniformVariable, imageHandler, samplingProperties); + } + else if (uniform->getType() == Type::FILENAMEARRAY) + { + StringVec filenamesStr; + if (uniform->getValue()) + { + filenamesStr = uniform->getValue()->asA(); + } + vector filenames; + for (const auto& filename : filenamesStr) + { + filenames.emplace_back(filename); + } + image = bindImageArray(filenames, uniformVariable, imageHandler, samplingProperties); + } if (image) { _boundImages.push_back(image); @@ -229,6 +243,50 @@ ImagePtr GlslMaterial::bindImage(const FilePath& filePath, const std::string& un return nullptr; } +ImagePtr GlslMaterial::bindImageArray(const vector& filePaths, 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. + ImagePtr image = imageHandler->acquireImage(filePaths[0], samplingProperties.defaultColor); + if (!image) + { + return nullptr; + } + + vector additionalImages; + for (unsigned int i = 1; i < filePaths.size(); ++i) + { + ImagePtr additionalImage = imageHandler->acquireImage(filePaths[i], samplingProperties.defaultColor); + additionalImages.emplace_back(additionalImage); + } + + // Bind the image and set its sampling properties. + if (imageHandler->bindImage(image, samplingProperties, additionalImages)) + { + GLTextureHandlerPtr textureHandler = std::static_pointer_cast(imageHandler); + int textureLocation = textureHandler->getBoundTextureLocation(image->getResourceId()); + if (textureLocation >= 0) + { + _glProgram->bindUniform(uniformName, Value::createValue(textureLocation), false); + return image; + } + } + return nullptr; +} + void GlslMaterial::bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr imageHandler, const ShadowState& shadowState) { if (!_glProgram) diff --git a/source/MaterialXRenderGlsl/GlslMaterial.h b/source/MaterialXRenderGlsl/GlslMaterial.h index 1ae945867a..61e9076d85 100644 --- a/source/MaterialXRenderGlsl/GlslMaterial.h +++ b/source/MaterialXRenderGlsl/GlslMaterial.h @@ -80,6 +80,11 @@ class MX_RENDERGLSL_API GlslMaterial : public ShaderMaterial ImageHandlerPtr imageHandler, const ImageSamplingProperties& samplingProperties) override; + protected: + ImagePtr bindImageArray(const vector& filePaths, const std::string& uniformName, ImageHandlerPtr imageHandler, + const ImageSamplingProperties& samplingProperties); + + public: /// Bind lights to shader. void bindLighting(LightHandlerPtr lightHandler, ImageHandlerPtr imageHandler, diff --git a/source/MaterialXRenderGlsl/GlslProgram.cpp b/source/MaterialXRenderGlsl/GlslProgram.cpp index b63e1faecc..132661c9da 100644 --- a/source/MaterialXRenderGlsl/GlslProgram.cpp +++ b/source/MaterialXRenderGlsl/GlslProgram.cpp @@ -1100,6 +1100,11 @@ int GlslProgram::mapTypeToOpenGLType(TypeDesc type) // A "filename" is not indicative of type, so just return a 2d sampler. return GL_SAMPLER_2D; } + else if (type == Type::FILENAMEARRAY) + { + // A "filenamearray" is not indicative of type, so just return a 2d array sampler. + return GL_SAMPLER_2D_ARRAY; + } return GlslProgram::Input::INVALID_OPENGL_TYPE; } diff --git a/source/MaterialXRenderMsl/MetalTextureHandler.h b/source/MaterialXRenderMsl/MetalTextureHandler.h index 7504c951ea..ece4d6f023 100644 --- a/source/MaterialXRenderMsl/MetalTextureHandler.h +++ b/source/MaterialXRenderMsl/MetalTextureHandler.h @@ -38,7 +38,7 @@ class MX_RENDERMSL_API MetalTextureHandler : public ImageHandler /// 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; + bool bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties, vector additionalImages = {}) override; protected: /// Bind an image. This method will bind the texture to an active texture @@ -58,7 +58,7 @@ class MX_RENDERMSL_API MetalTextureHandler : public ImageHandler id getMTLSamplerStateForImage(unsigned int index); /// Create rendering resources for the given image. - bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false) override; + bool createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget = false, vector additionalImages = {}) override; /// Release rendering resources for the given image, or for all cached images /// if no image pointer is specified. diff --git a/source/MaterialXRenderMsl/MetalTextureHandler.mm b/source/MaterialXRenderMsl/MetalTextureHandler.mm index 6a8b5c2ef4..eda63b8d57 100644 --- a/source/MaterialXRenderMsl/MetalTextureHandler.mm +++ b/source/MaterialXRenderMsl/MetalTextureHandler.mm @@ -19,12 +19,12 @@ _device = device; } -bool MetalTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties) +bool MetalTextureHandler::bindImage(ImagePtr image, const ImageSamplingProperties& samplingProperties, vector additionalImages) { // Create renderer resources if needed. if (image->getResourceId() == MslProgram::UNDEFINED_METAL_RESOURCE_ID) { - if (!createRenderResources(image, true)) + if (!createRenderResources(image, true, false, additionalImages)) { return false; } @@ -63,7 +63,7 @@ // Create renderer resources if needed. if (image->getResourceId() == MslProgram::UNDEFINED_METAL_RESOURCE_ID) { - if (!createRenderResources(image, true)) + if (!createRenderResources(image, true, false)) { return false; } @@ -128,10 +128,21 @@ return false; } -bool MetalTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget) +bool MetalTextureHandler::createRenderResources(ImagePtr image, bool generateMipMaps, bool useAsRenderTarget, vector additionalImages) { id texture = nil; + for (const auto& additionalImage : additionalImages) + { + if ((image->getWidth() != additionalImage->getWidth()) || + (image->getHeight() != additionalImage->getHeight()) || + (image->getBaseType() != additionalImage->getBaseType()) || + (image->getChannelCount() != additionalImage->getChannelCount())) + { + throw Exception("All images in a Texture Array must have the same texture geometry."); + } + } + MTLPixelFormat pixelFormat; MTLDataType dataType; @@ -144,7 +155,16 @@ false, dataType, pixelFormat); MTLTextureDescriptor* texDesc = [MTLTextureDescriptor new]; - [texDesc setTextureType:MTLTextureType2D]; + if (additionalImages.empty()) + { + [texDesc setTextureType:MTLTextureType2D]; + } + else + { + [texDesc setTextureType:MTLTextureType2DArray]; + texDesc.arrayLength = additionalImages.size() + 1; + } + texDesc.width = image->getWidth(); texDesc.height = image->getHeight(); texDesc.mipmapLevelCount = generateMipMaps ? image->getMaxMipCount() : 1; @@ -188,80 +208,96 @@ 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) + auto addImageToTexture = [texture, &blitCmdEncoder, &rearrangedDataC, &rearrangedDataF, pixelFormat](id device, ImagePtr image, NSUInteger sliceIndex) { - bool isFloat = pixelFormat == MTLPixelFormatRGBA32Float; + NSUInteger channelCount = image->getChannelCount(); - sourceBytesPerRow = sourceBytesPerRow / 3 * 4; - sourceBytesPerImage = sourceBytesPerImage / 3 * 4; + NSUInteger sourceBytesPerRow = + image->getWidth() * + channelCount * + getTextureBaseTypeSize(image->getBaseType()); + NSUInteger sourceBytesPerImage = + sourceBytesPerRow * + image->getHeight(); - size_t srcIdx = 0; + void* imageData = image->getResourceBuffer(); + rearrangedDataF.clear(); + rearrangedDataC.clear(); - if (isFloat) + if ((pixelFormat == MTLPixelFormatRGBA32Float || pixelFormat == MTLPixelFormatRGBA8Unorm) && channelCount == 3) { - rearrangedDataF.resize(sourceBytesPerImage / sizeof(float)); - for (size_t dstIdx = 0; dstIdx < rearrangedDataF.size(); ++dstIdx) + bool isFloat = pixelFormat == MTLPixelFormatRGBA32Float; + + sourceBytesPerRow = sourceBytesPerRow / 3 * 4; + sourceBytesPerImage = sourceBytesPerImage / 3 * 4; + + size_t srcIdx = 0; + + if (isFloat) { - if ((dstIdx & 0x3) == 3) + rearrangedDataF.resize(sourceBytesPerImage / sizeof(float)); + for (size_t dstIdx = 0; dstIdx < rearrangedDataF.size(); ++dstIdx) { - rearrangedDataF[dstIdx] = 1.0f; - continue; + if ((dstIdx & 0x3) == 3) + { + rearrangedDataF[dstIdx] = 1.0f; + continue; + } + + rearrangedDataF[dstIdx] = ((float*) imageData)[srcIdx++]; } - rearrangedDataF[dstIdx] = ((float*) imageData)[srcIdx++]; + imageData = rearrangedDataF.data(); } - - imageData = rearrangedDataF.data(); - } - else - { - rearrangedDataC.resize(sourceBytesPerImage); - for (size_t dstIdx = 0; dstIdx < rearrangedDataC.size(); ++dstIdx) + else { - if ((dstIdx & 0x3) == 3) + rearrangedDataC.resize(sourceBytesPerImage); + for (size_t dstIdx = 0; dstIdx < rearrangedDataC.size(); ++dstIdx) { - rearrangedDataC[dstIdx] = 255; - continue; + if ((dstIdx & 0x3) == 3) + { + rearrangedDataC[dstIdx] = 255; + continue; + } + + rearrangedDataC[dstIdx] = ((unsigned char*) imageData)[srcIdx++]; } - rearrangedDataC[dstIdx] = ((unsigned char*) imageData)[srcIdx++]; + imageData = rearrangedDataC.data(); } - imageData = rearrangedDataC.data(); + channelCount = 4; } - channelCount = 4; - } + if (imageData) + { + id buffer = [device newBufferWithBytesNoCopy:imageData + length:sourceBytesPerImage + options:MTLStorageModeShared + deallocator:nil]; + [blitCmdEncoder copyFromBuffer:buffer + sourceOffset:0 + sourceBytesPerRow:sourceBytesPerRow + sourceBytesPerImage:sourceBytesPerImage + sourceSize:MTLSizeMake(image->getWidth(), image->getHeight(), 1) + toTexture:texture + destinationSlice:sliceIndex + destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + + [buffer release]; + } + }; + + addImageToTexture(_device, image, 0); - id buffer = nil; - if (imageData) + NSUInteger sliceIndex = 1; + for (const auto& image : additionalImages) { - 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)]; + addImageToTexture(_device, image, sliceIndex++); } if (generateMipMaps && image->getMaxMipCount() > 1) @@ -272,9 +308,6 @@ [cmdBuffer commit]; [cmdBuffer waitUntilCompleted]; - if (buffer) - [buffer release]; - return true; } diff --git a/source/MaterialXRenderMsl/MslPipelineStateObject.h b/source/MaterialXRenderMsl/MslPipelineStateObject.h index a823662f5f..f9a14e5ad9 100644 --- a/source/MaterialXRenderMsl/MslPipelineStateObject.h +++ b/source/MaterialXRenderMsl/MslPipelineStateObject.h @@ -266,10 +266,17 @@ class MX_RENDERMSL_API MslProgram ImageHandlerPtr imageHandler); // Bind an individual texture to a program uniform location - ImagePtr bindTexture(id renderCmdEncoder, - unsigned int uniformLocation, - ImagePtr imagePtr, - ImageHandlerPtr imageHandler); + ImagePtr bindMetalTexture(id renderCmdEncoder, + unsigned int uniformLocation, + ImagePtr imagePtr, + ImageHandlerPtr imageHandler); + + // Bind an individual texture to a program uniform location + ImagePtr bindTextureArray(id renderCmdEncoder, + unsigned int uniformLocation, + vector filePaths, + ImageSamplingProperties samplingProperties, + ImageHandlerPtr imageHandler); void bindUniformBuffers(id renderCmdEncoder, LightHandlerPtr lightHandler, diff --git a/source/MaterialXRenderMsl/MslPipelineStateObject.mm b/source/MaterialXRenderMsl/MslPipelineStateObject.mm index 470fa549b2..dd4d092f5c 100644 --- a/source/MaterialXRenderMsl/MslPipelineStateObject.mm +++ b/source/MaterialXRenderMsl/MslPipelineStateObject.mm @@ -539,6 +539,18 @@ int GetStrideOfMetalType(MTLDataType type) _indexBufferIds.clear(); } +ImagePtr MslProgram::bindMetalTexture(id renderCmdEncoder, + unsigned int uniformLocation, + ImagePtr image, + ImageHandlerPtr imageHandler) +{ + if (static_cast(imageHandler.get())->bindImage(renderCmdEncoder, uniformLocation, image)) + { + return image; + } + return nullptr; +} + ImagePtr MslProgram::bindTexture(id renderCmdEncoder, unsigned int uniformLocation, const FilePath& filePath, @@ -546,24 +558,34 @@ int GetStrideOfMetalType(MTLDataType type) ImageHandlerPtr imageHandler) { // Acquire the image. - string error; ImagePtr image = imageHandler->acquireImage(filePath, samplingProperties.defaultColor); imageHandler->bindImage(image, samplingProperties); - return bindTexture(renderCmdEncoder, uniformLocation, image, imageHandler); + return bindMetalTexture(renderCmdEncoder, uniformLocation, image, imageHandler); } -ImagePtr MslProgram::bindTexture(id renderCmdEncoder, - unsigned int uniformLocation, - ImagePtr image, - ImageHandlerPtr imageHandler) +ImagePtr MslProgram::bindTextureArray(id renderCmdEncoder, + unsigned int uniformLocation, + vector filePaths, + ImageSamplingProperties samplingProperties, + ImageHandlerPtr imageHandler) { - // Acquire the image. - string error; - if (static_cast(imageHandler.get())->bindImage(renderCmdEncoder, uniformLocation, image)) + ImagePtr mainImage = nullptr; + vector additionalImages; + for (const auto& filePath : filePaths) { - return image; + ImagePtr image = imageHandler->acquireImage(filePath, samplingProperties.defaultColor); + if (!mainImage) + { + mainImage = image; + } + else + { + additionalImages.emplace_back(image); + } } - return nullptr; + + imageHandler->bindImage(mainImage, samplingProperties, additionalImages); + return bindMetalTexture(renderCmdEncoder, uniformLocation, mainImage, imageHandler); } MaterialX::ConstValuePtr MslProgram::findUniformValue(const string& uniformName, @@ -612,7 +634,7 @@ int GetStrideOfMetalType(MTLDataType type) samplingProperties.filterType = ImageSamplingProperties::FilterType::LINEAR; static_cast(imageHandler.get())->bindImage(env.second, samplingProperties); - bindTexture(renderCmdEncoder, (unsigned int) arg.index, env.second, imageHandler); + bindMetalTexture(renderCmdEncoder, (unsigned int) arg.index, env.second, imageHandler); found = true; } } @@ -628,7 +650,7 @@ int GetStrideOfMetalType(MTLDataType type) if (image && (image->getWidth() > 1 || image->getHeight() > 1)) { - bindTexture(renderCmdEncoder, (unsigned int) arg.index, image, imageHandler); + bindMetalTexture(renderCmdEncoder, (unsigned int) arg.index, image, imageHandler); found = true; } } @@ -638,7 +660,6 @@ int GetStrideOfMetalType(MTLDataType type) 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; { @@ -648,7 +669,22 @@ int GetStrideOfMetalType(MTLDataType type) } samplingProperties.setProperties(uniformNameWithoutPostfix, publicUniforms); samplingProperties.enableMipmaps = _enableMipMapping; - bindTexture(renderCmdEncoder, (unsigned int) arg.index, fileName, samplingProperties, imageHandler); + + if (uniform->second->typeString == "filename") + { + string fileName = uniform->second->value ? uniform->second->value->getValueString() : ""; + bindTexture(renderCmdEncoder, (unsigned int) arg.index, fileName, samplingProperties, imageHandler); + } + else if (uniform->second->typeString == "filenamearray") + { + StringVec filenamesStr = uniform->second->value ? uniform->second->value->asA() : StringVec(); + vector filenames; + for (const auto& filename : filenamesStr) + { + filenames.emplace_back(filename); + } + bindTextureArray(renderCmdEncoder, (unsigned int) arg.index, filenames, samplingProperties, imageHandler); + } } } } @@ -1058,11 +1094,16 @@ int GetStrideOfMetalType(MTLDataType type) { auto inputIt = _uniformList.find(variableName); - if (inputIt == _uniformList.end()) { - if(variableTypeDesc == Type::FILENAME) + if (inputIt == _uniformList.end()) + { + if (variableTypeDesc == Type::FILENAME) { inputIt = _uniformList.find(TEXTURE_NAME(variableName)); } + else if (variableTypeDesc == Type::FILENAMEARRAY) + { + inputIt = _uniformList.find(TEXTUREARRAY_NAME(variableName)); + } else { inputIt = _uniformList.find(uniforms.getInstance() + "." + variableName); @@ -1101,7 +1142,7 @@ int GetStrideOfMetalType(MTLDataType type) for (size_t i = 0, n = members.size(); i < n; ++i) { const auto& structMember = members[i]; - auto memberVariableName = variableName+"."+structMember._name; + auto memberVariableName = variableName + "." + structMember._name; auto memberVariableValue = aggregateValue->getMemberValue(i); populateUniformInput_ref(structMember._typeDesc, memberVariableName, memberVariableValue, populateUniformInput_ref); @@ -1494,6 +1535,11 @@ throw ExceptionRenderError( // A "filename" is not indicative of type, so just return a 2d sampler. return MTLDataTypeTexture; } + else if (type == Type::FILENAMEARRAY) + { + // A "filenamearray" is not indicative of type, so just return a 2d sampler. + return MTLDataTypeTexture; + } else if (type == Type::BSDF || type == Type::MATERIAL || type == Type::DISPLACEMENTSHADER || diff --git a/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp b/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp index 4bec0016de..28c9001b08 100644 --- a/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp +++ b/source/MaterialXTest/MaterialXGenMdl/GenMdl.cpp @@ -18,7 +18,6 @@ #include #include - namespace mx = MaterialX; TEST_CASE("GenShader: MDL Syntax", "[genmdl]") @@ -91,6 +90,7 @@ TEST_CASE("GenShader: MDL Implementation Check", "[genmdl]") mx::StringSet generatorSkipNodeTypes; generatorSkipNodeTypes.insert("light"); + generatorSkipNodeTypes.insert("imagearray"); mx::StringSet generatorSkipNodeDefs; diff --git a/source/MaterialXTest/MaterialXGenMdl/GenMdl.h b/source/MaterialXTest/MaterialXGenMdl/GenMdl.h index e51dbbd7e5..25d506840c 100644 --- a/source/MaterialXTest/MaterialXGenMdl/GenMdl.h +++ b/source/MaterialXTest/MaterialXGenMdl/GenMdl.h @@ -37,6 +37,7 @@ class MdlShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester _skipNodeDefs.insert("ND_spot_light"); _skipNodeDefs.insert("ND_directional_light"); _skipNodeDefs.insert("ND_dot_"); + _skipNodeDefs.insert("ND_elementat_"); ParentClass::addSkipNodeDefs(); } @@ -50,10 +51,12 @@ class MdlShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester _skipFiles.insert("heighttonormal_in_nodegraph.mtlx"); } + _skipFiles.insert("imagearray.mtlx"); + _skipFiles.insert("elementat.mtlx"); + ShaderGeneratorTester::addSkipFiles(); } - // Ignore light shaders in the document for MDL void findLights(mx::DocumentPtr /*doc*/, std::vector& lights) override { @@ -81,7 +84,7 @@ class MdlShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester "geompropvalue", "surfacematerial", "volumematerial", "IM_absorption_vdf_", "IM_mix_vdf_", "IM_add_vdf_", "IM_multiply_vdf", "IM_measured_edf_", "IM_blackbody_", "IM_conical_edf_", - "IM_displacement_", "IM_volume_", "IM_light_" + "IM_displacement_", "IM_volume_", "IM_light_", "IM_elementat_" }; ShaderGeneratorTester::getImplementationWhiteList(whiteList); } diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp index 6e613c85b8..ce37284bef 100644 --- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp +++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp @@ -87,6 +87,7 @@ TEST_CASE("GenShader: OSL Implementation Check", "[genosl]") mx::StringSet generatorSkipNodeTypes; generatorSkipNodeTypes.insert("light"); + generatorSkipNodeTypes.insert("imagearray"); mx::StringSet generatorSkipNodeDefs; diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.h b/source/MaterialXTest/MaterialXGenOsl/GenOsl.h index cc97558e59..2b569fdc7e 100644 --- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.h +++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.h @@ -40,11 +40,12 @@ class OslShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester // Arnold specific files are ignored in vanilla osl target void addSkipLibraryFiles() override { - _skipLibraryFiles.insert( "pbrlib_genosl_arnold_impl.mtlx" ); + _skipLibraryFiles.insert("pbrlib_genosl_arnold_impl.mtlx"); } void addSkipFiles() override { + _skipFiles.insert("imagearray.mtlx"); } // Ignore light shaders in the document for OSL