diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c1c4bade..40e6ff30fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,14 @@ ### Added - Added support for 'nodedef' attributes on MaterialX\:\:Node, integrating this usage into GraphElement\:\:addNodeInstance. +- Added the MaterialX\:\:GeomPropDef class for geometric input declarations. +- Added the Document\:\:getGeomAttrValue method. +- Added the ValueElement\:\:getResolvedValue method. - Added support for GCC 8 and Clang 7. ### Changed - Added callbacks Observer\:\:onCopyContent and Observer\:\:onClearContent, and removed callback Observer::onInitialize. +- Moved the standard document library to the 'documents/Libraries/stdlib' folder. ## [1.36.1] - 2018-12-18 diff --git a/README.md b/README.md index 5ebc77f8f6..8dab5121ae 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,9 @@ The Python bindings for MaterialX are based on [PyBind11](https://github.com/pyb The MaterialX repository consists of the following folders: - documents - The MaterialX specification, developer guide, and example files. - source - A cross-platform C++ library for MaterialX with Python bindings. - The MaterialXCore module supports the core MaterialX elements - and graph traversal, while the MaterialXFormat module supports - XML serialization. - python - Support modules for MaterialX Python. +- [documents](documents) - The MaterialX specification, developer guide, example and test suite files. +- [source](source) - Cross-platform C++ libraries for MaterialX with Python bindings. +- [python](python) - Support modules for MaterialX Python. ### Usage @@ -39,4 +36,4 @@ Use of this code is subject to the terms of the Autodesk license agreement provi ### Additional Resources -- The [Developer Guide](http://www.materialx.org/docs/api/index.html) contains more detailed documentation and code examples in C++ and Python. \ No newline at end of file +- The [Developer Guide](http://www.materialx.org/docs/api/index.html) contains more detailed documentation and code examples in C++ and Python. diff --git a/documents/DeveloperGuide/README.md b/documents/DeveloperGuide/README.md new file mode 100644 index 0000000000..8f8e8fb257 --- /dev/null +++ b/documents/DeveloperGuide/README.md @@ -0,0 +1,5 @@ +# Developer Information + +- [MainPage.md](MainPage.md) : Main developer information +- [CodeExamples.md](CodeExamples.md) : Code examples +- [ShaderGeneration.md](ShaderGeneration.md) : Shader generation documentation. diff --git a/documents/DeveloperGuide/ShaderGeneration.md b/documents/DeveloperGuide/ShaderGeneration.md new file mode 100644 index 0000000000..130c5cffaf --- /dev/null +++ b/documents/DeveloperGuide/ShaderGeneration.md @@ -0,0 +1,486 @@ +# Shader Generation + +## 1.1 Scope +The shader generation features of ShaderX are implemented as an extension to MaterialX. Most +features are contained in a the [MaterialXGenShader](/source/MaterialXGenShader) shared library, but some features are part of the [MaterialXCore](/source/MaterialXCore) library as well. + +Note that ShaderX has no runtime and the output produced is source code, not binary executable +code. The source code produced needs to be compiled by a shading language compiler before being +executed by the renderer. See Figure 1 for a high level overview of the system. + +![ShaderX with multiple shader generators](../Images/shaderx.png) + +**Figure 1**: ShaderX with multiple shader generators + +## 1.2 Languages and Shader Generators +The ShaderX description is free from device specific details and all implementation details needs to be taken care of by the shader generators. There is one shader generator for each supported shading language. However for each language there can also be variations needed for different renderers. + +For example: `OpenGL` renderers supporting `GLSL` can use forward rendering or deferred rendering, +each with very different requirements for how the shaders are constructed. Another example is +different renderers supporting OSL but with different sets of closures or closure parameters. Hence a separate shader generator can be defined for each language/target combination. + +To add a new shader generator for a target you add a new C++ class derived from the base class +`ShaderGenerator`, or one of the existing derived shader generator classes (`HwShaderGenerator`, +`GlslShaderGenerator`, `OslShaderGenerator`, etc.), and override the methods you need to customize. You might also need to derive a new `Syntax` class, which is used to handle syntactical +differences between different shading languages. Then you need to make sure there are implementations defined for all the nodes you want to support, standard library nodes and nodes from other libraries, by either reusing existing implementations where applicable or adding in new ones. + +See [1.3](1.3 Node Implementations) on how that is done. + +Note that a shader generator doesn’t need to be defined at the time when node definitions are +added. New shader generators can be added later, and node implementations for new targets can +be added for existing nodes. + +## 1.3 Node Implementations +There are four different methods to define the implementation of a node in ShaderX: +- Using an inline expression. +- Using a function written in the target language. +- Using a nodegraph that defines the operation performed by the node. +- Using a C++ class that emits code dynamically during shader generation. + +For all methods the implementation is tied to a specific `nodedef` with a well defined interface of typed inputs and outputs. In the following sub-sections each of these methods are explained. + +### 1.3.1 Inline Expression +ShaderX’s code generators support a very simple expression language for inlining code. This is +useful for simple nodes where the operation can be expressed as a single line of code. Inlining will reduce the number of function calls and produce more compact code. The syntax to use is the +same as the target shading language, with the addition of using the node’s input ports as variables wrapped in double curly brackets: `{{input}}`. The code generator will replace these variables with values assigned or connected to the respective inputs. Figure 2 gives an example. + +Connecting the expression to the nodedef is done using an `` element as seen in +Figure 2. The file extension is used to differentiate inline expressions from source code functions, using `filename.inline`. + +```xml +// Nodedef elements for node + + + + + + + + +<... more types ...> + +// Implementation elements for node + + +<... more types ...> + +// Nodedef elements for node + + + + + + + + + + +<... more types ...> +``` +```xml +// Implementation elements for node + + +<... more types ...> +``` +**Figure 2**: Inline expressions for implementing nodes `` and ``. + +### 1.3.2 Shading Language Function +For nodes that can’t be implemented by inline expressions a function definition can be used instead. The function signature should match the nodedefs interface with inputs and outputs. See Figure 3 for an example. Connecting the source code to the nodedef is done using an element, see [1] for more information. + +```xml +// Nodedef element + + + + + + + + + + + + + +// Implementation element + +``` +```c++ +// File 'mx_image_color3.osl' contains: +void mx_image_color3(string file, string layer, color defaultvalue, + vector2 texcoord, string filtertype, + string uaddressmode, string vaddressmode, + string framerange, int frameoffset, + string frameendaction, output color out) +{ + // Sample the texture + out = texture(file, texcoord.x, texcoord.y, + "interp", filtertype, + "subimage", layer, + "missingcolor", defaultvalue, + "wrap", uaddressmode); +} +``` +**Figure 3**: Shading language functions implementation for node `` in OSL. + +### 1.3.3 Implementation by Node Graph +As an alternative to define source code, there is also an option to reference a nodegraph as the +implementation of a nodedef. The only requirement is that the nodegraph and nodedef have +matching inputs and outputs. + +This is useful for creating a compound for a set of nodes performing some common operation. It +can then be referenced as a node inside other nodegraphs. It is also useful for creating compatibility graphs for unknown nodes. If a node is created by some third party, and its implementation is unknown or proprietary, a compatibility graph can be created using known nodes and be referenced as a stand-in implementation. Linking a nodegraph to a nodedef is done by simply setting a nodedef attribute on the nodegraph definition. See Figure 4 for an example. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +**Figure 4**: Implementation using a nodegraph. + +### 1.3.4 Dynamic Code Generation +In some situations static source code is not enough to implement a node. The code might need +to be customized depending on parameters set on the node. Or for a HW render target vertex +streams or uniform inputs might need to be created in order to supply the data needed for the +node implementation. + +In this case a C++ class can be added to ShaderX to handle the implementation of the node. The +class should be derived from the base class `SgImplementation`. It should specify what language +and target it is for by overriding `getLanguage()` and `getTarget()`. It can also be specified to +support all languages or all targets by setting the identifier to an empty string, as done for the +target identifier in the example below. It then needs to be registered for a `ShaderGenerator` by +calling `ShaderGenerator::registerImplementation()`. See Figure 5 for an example. +When an `SgImplementation` class is used for a nodedef the corresponding `` +element don’t need a file attribute, since no static source code is used. The `` +element will then act only as a declaration that there exists an implementation for the nodedef for a particular language and target. + +Note that the use of `SgImplementation` classes for dynamic code generation goes against our +design goal of being data driven. An application needs to be recompiled after adding a new node +implementation class. However this usage is only needed in special cases. And in these cases +the code produced can be made much more efficient by allowing this dynamic generation. As +a result we choose to support this method of code generation, but it should only be used when +inline expressions or static source code functions are not enough to handle the implementation of +a node. + +```c++ +/// Implementation of ’foo' node for OSL +class FooOsl : public SgImplementation +{ +public: + static SgImplementationPtr create() { return std::make_shared(); } + + const string& getLanguage() const override { return LANGUAGE_OSL; } + const string& getTarget() const override { return EMPTY_STRING; } + + void emitFunctionDefinition(const SgNode& node, ShaderGenerator& sg, + Shader& shader) override + { + // Emit function definition if needed for the node + } + + void emitFunctionCall(const SgNode& node, const SgNodeContext& context, + ShaderGenerator& sg, Shader& shader) override + { + // Emit function call, or inline shader code, for the node + } +}; +``` +```c++ +OslShaderGenerator::OslShaderGenerator() + : ShaderGenerator(std::make_shared()) +{ + ... + // Register foo implementation for nodedefs it should be used for + registerImplementation("IM_foo_color2_osl", FooOsl::create); + registerImplementation("IM_foo_color3_osl", FooOsl::create); + registerImplementation("IM_foo_color4_osl", FooOsl::create); + ... +} +``` +**Figure 5**: C++ class for dynamic code generation. + +## 1.4 Shader Generation Steps +This section outlines the steps taken in general to produce a shader from the MaterialX description. The `ShaderGenerator` base class and its support classes will handle this for you, but it’s good to +know the steps involved in case more custom changes are needed to support a new target. + +ShaderX supports generating a shader starting from either a graph output port, an arbitrary node +inside a graph, or a shaderref in a material. A shader is generated by calling your shader generator +class with an element of either of these types as input. The given element and all dependencies +upstream will be translated into a single monolithic shader in the target shading langage. + +```c++ +// Generate a shader starting from the given element, translating +// the element and all dependencies upstream into shader code. +ShaderPtr ShaderGenerator::generate(const string& shaderName, + ElementPtr element, + const SgOptions& options) +``` + +The shader generation process can be divided into initialization and code generation. The initial- +ization is handled by the `Shader` class, and consists of a number of steps: + +1. Create an optimized version of the graph as a tree with the given element as root, and with only the used dependencies connected upstream. This involves removing unused paths in +the graph, converting constant nodes to constant values, and adding in any default nodes +for ports that are unconnected but has default connections specified. Removal of unused +paths typically involves constant folding and pruning of conditional branches that never will +be taken. Since the resulting shader in the end will be compiled by a shading language +compiler, and receive a lot of additional optimizations, we don’t need to do too much work +in this optimization step. However a few graph level optimizations can make the resulting +shader a lot smaller and save time and memory during shader compilation. It will also +produce more readable source code which is good for debugging purposes. + + This step is also a good place to do other custom optimizations needed by a particular target. +For example simplification of the graph, which could involve substituting expensive nodes +with approximate nodes, identification of common subgraphs that can be merged, etc. + +2. Track which graph interface ports are being used. It’s done by finding which graph interface +ports have connections to nodes inside the graph. This is useful in later steps to be able to +ignore interface ports that are not used. + +3. The nodes are sorted in topological order. Since a node can be referenced by many other +nodes in the graph we need an ordering of the nodes so that nodes that has a dependency +on other nodes comes after all nodes it depends on. This step also makes sure there are no +cyclic dependencies in the graph. + +4. The shader signature; its interface of uniforms and varyings are established. This consists of +the graph interface ports that are in use, as well as internal ports that has been published +to the interface (an example of the latter is for a HW shader generator where image texture +filenames gets converted to texture samplers which needs to be published in order to be +bound by the target application). This information is stored on the Shader class, and can +be retrieved from it, together with the emitted source code when generation is completed. + +5. Information about scope is tracked for each node. This information is needed to handle +branching by conditional nodes. For example, if a node is used only by a particular branch +on a varying conditional we want to calculate this node only inside that scope, when that +corresponding branch is taken. A node can be used in global scope, in a single conditional +scope or by multiple conditional scopes. + + +The output from the initialization step is a new graph representation constructed using the classes +`SgNode`, `SgInput`, `SgOutput`, `SgNodeGraph`, etc. This is a graph representation optimized for +shader generation with quick access and traversal of nodes and ports, as well as caching of extra +information needed by shader generation. + +After initialization the code generation steps are handled by the `ShaderGenerator` class and derived classes. This part is specific for the particular generator being used, but in general it consists of the following steps: + +1. Typedefs are emitted as specified by the Syntax class. +2. Function definitions are emitted for all the atomic nodes that has shading language functions +for their implementations. For nodes using dynamic code generation their `SgImplementation` +instances are called to generate the functions. For nodes that are implemented by graphs a +function definition representing the graph computation is emitted. +3. The shader signature is emitted with all uniforms set to default values. The shader uniforms +can later be accessed on the returned `Shader` instance in order for applications to be able to +bind values to them. +4. The function calls for all nodes are emitted, in the right dependency order, propagating +output results from upstream nodes as inputs to downstream nodes. Inline expressions are +emitted instead of functions calls for nodes that use this. +5. The final shader output is produced and assigned to the shader output variable. + +Note that if a single monolithic shader for the whole graph is not appropriate for your system the +generator can be called on elements at any point in your graph, and generate code for sub-parts. +It is then up to the application to decide where to split the graph, and to assemble the shader code for sub-parts after all have been generated. + +## 1.5 Bindings and Shading Context + +There are a number of ways to bind values to input ports on nodes and graphs. A port can be +assigned a constant default value or be connected to other nodes. If a port is connected it will +read the value from the upstream node. Ports can also be set to have a default node connected +if the user has not made a specific connection to it. This is for example used for input ports +that expect to receive geometric data from the current shading context, like normal or texture +coordinates. If no such connection is made explicitly a default geometric node is connected to +supply the data. + +Geometric data from the current shading context is supplied using MaterialX’s geometric nodes. +There are a number of predefined geometric nodes in the MaterialX standard library to supply +shading context data like position, normal, tangents, texture coordinates, vertex colors, etc. The +vectors can be returned in different coordinate spaces: model, object or world space. If the data +available from the standard geometric nodes are not enough the general purpose primvar node +`geomattr` can be used to access any named data on geometry using a string identifier. It is up to +the shader generator and node implementation of these geometric nodes to make sure the data is +supplied, and where applicable transformed to the requested coordinate space. + +## 1.6 Shader Stages + +The Shader base class supports multiple shader stages. This is needed in order to generate separate +code for multiple stages on HW render targets. By default the base class has only a single stage, +called the pixel stage. But there is another sub-class `HwShader` that adds an additional stage, the +vertex stage. If more stages are needed you can sub-class and extend on this. +When creating shader input variables you can specify which stage the variable should be used in, +see 1.7 for more information on shader variable creation. + +Node implementations using static source code (function or inline expressions) are always emitted +to the pixel stage. Controlling the vertex stage, or other stages, is not supported using static source +code. In order to do that you must use dynamic code generation with a custom `SgImplementation` +sub-class for your node. You are then able to control how it affects all stages separately. Inside +`emitFunctionDefinition` and `emitFunctionCall` you can add separate sections for each stage +using begin/end shader stage macros. Figure 6 shows how the texcoord node for GLSL is emitting +different code into the vertex and pixel stages. + +## 1.7 Shader Variables +When generating a shader from a node graph or shaderref the inputs and parameters on those +elements will be published as shader uniforms on the resulting shader. A listing of the created uniforms can be red from the produced Shader instance. The shader uniforms can then be presented +to the user have their values set by the application. + +Adding new uniforms to a shader is done by first creating a uniform block and then adding +uniforms into the block. There are two predefined uniform blocks that can be used directly, one +named `PublicUniforms` and another named `PrivateUniforms`. Public is used for uniforms to +be published to the user, as described above, and private is used for uniforms needed by node +implementations but set by the application and not published. All uniform blocks can be queried +and accessed by the application from the `Shader` instance after generation. + +```c++ +// Implementation of 'texcoord' node for GLSL +class TexCoordGlsl : public SgImplementation +{ +public: + static SgImplementationPtr create() { return std::make_shared(); } + + void createVariables(const SgNode& node, ShaderGenerator& sg, Shader& s) override + { + HwShader& shader = static_cast(s); + + const SgOutput* output = node.getOutput(); + const SgInput* indexInput = node.getInput(INDEX); + const string index = indexInput ? indexInput->value->getValueString() : "0"; + + shader.createAppData(output->type, "i_texcoord_" + index); + shader.createVertexData(output->type, "texcoord_" + index); + } + + void emitFunctionCall(const SgNode& node, ShaderGenerator& sg, Shader& s) override + { + HwShader& shader = static_cast(s); + + const string& blockInstance = shader.getVertexDataBlock().instance; + const string blockPrefix = blockInstance + "."; + + const SgInput* indexInput = node.getInput(INDEX); + const string index = indexInput ? indexInput->value->getValueString() : "0"; + const string variable = "texcoord_" + index; + + // For the vertex stage set texcoords from the requested uv-set + BEGIN_SHADER_STAGE(shader, HwShader::VERTEX_STAGE) + if (!shader.isCalculated(variable)) + { + shader.addLine(blockPrefix + variable + " = i_" + variable); + shader.setCalculated(variable); + } + END_SHADER_STAGE(shader, HwShader::VERTEX_STAGE) + + // For the pixel stage return the texcoords set in the vertex stage + BEGIN_SHADER_STAGE(shader, HwShader::PIXEL_STAGE) + shader.beginLine(); + sg.emitOutput(node.getOutput(), true, shader); + shader.addStr(" = " + blockPrefix + variable); + shader.endLine(); + END_SHADER_STAGE(shader, HwShader::PIXEL_STAGE) + } +}; +``` +**Figure 6**: Implementation of node `texcoord` in GLSL. Using an `SgImplementation` sub-class in order to control shader variable creation and code generation into separate shader stages. + +For inputs representing geometric data streams a separate variable block is used. In ShaderX +these variables are named application data inputs (in a HW shading language they are often +named varying inputs or attributes). The `Shader` class has a method for creating such variables, +in a block named `AppData`. This is also accessible from the `Shader` instance after generation. +In order to pass data between shader stages in a hardware shader additional variable blocks are +needed. For this purpose the `HwShader` class (derived from `Shader`) contains a variable block named `VertexData`. It is used for transporting data from the vertex stage to the pixel stage. The +HwShader class has methods for creating such vertex data variables. + +Creating shader variables and binding values to them needs to be done in agreement with the +shader generator side and application side. The application must know what a variable is for in +order to bind meaningful data to it. One way of handling this is by using semantics. All shader +variables created in ShaderX can be assigned a semantic if that is used by the target application. +ShaderX does not impose a specific set of semantics to use, so for languages and applications that +use this any semantics can be used. For languages that does not use semantics a variable naming +convention needs to be used instead. + +Figure 6 shows how creation of shader inputs and vertex data variables are done for a node +implementation that needs this. + +### 1.7.1 Variable Naming Convention + +ShaderX’s built-in shader generators and accompanying node implementations are using a naming convention for its shader variables. A custom shader generator that derives from and takes +advantage of built-in features should preferably use the same convention. +Uniform variables are prefixed with `u_` and application data inputs with `i_` . For languages not using semantics. Figure 7 shows the naming used for variables (inputs and uniforms) with predefined binding rules: + +App data input variables + +| NAME | TYPE | BINDING | +| :--- | :--: | :--- | +| i_position | vec3 | Vertex position in object space. | +| i_normal | vec3 | Vertex normal in object space. | +| i_tangent | vec3 | Vertex tangent in object space. | +| i_bitangent | vec3 | Vertex bitangent in object space. | +| i_texcoord_N | vec2 | Vertex texture coord for N:th uv set. | +| i_color_N | vec4 | Vertex color for N:th color set. | + + +Uniform variables + +| NAME | TYPE | BINDING | +| :--- | :--: | :--- | +| u_worldMatrix | mat4 | World transform. | +| u_worldInverseMatrix | mat4 | World transform, inverted. | +| u_worldTransposeMatrix | mat4 | World transform, transposed. | +| u_worldInverseTransposeMatrix | mat4 | World transform, inverted, transposed. | +| u_viewMatrix | mat4 | View transform. | +| u_viewInverseMatrix | mat4 | View transform, inverted. | +| u_viewTransposeMatrix | mat4 | View transform, transposed. +| u_viewInverseTransposeMatrix | mat4 | View transform, inverted, transposed. | +| u_projectionMatrix | mat4 | Projection transform. | +| u_projectionInverseMatrix | mat4 | Projection transform, inverted. | +| u_projectionTransposeMatrix | mat4 | Projection transform, transposed. | +| u_projectionInverseTransposeMatrix | mat4 | Projection transform, inverted, transposed. | +| u_worldViewMatrix | mat4 | World-view transform. | +| u_viewProjectionMatrix | mat4 | View-projection transform. | +| u_worldViewProjectionMatrix | mat4 | World-view-projection transform. | +| u_viewPosition | vec3 | World-space position of the viewer. | +| u_viewDirection | vec3 | World-space direction of the viewer. | +| u_frame | float | The current frame number as defined by the host application. | +| u_time | float | The current time in seconds. | +| u_geomattr_ | | A named attribute of given where is the name of the variable on the geometry. | +| u_numActiveLightSources | int | The number of currently active light sources. Note that in shader this is clamped against the maximum allowed number of light sources. | +| u_lightData[] | struct | Array of struct LightData holding parameters for active light sources. The `LightData` struct is built dynamically depending on requirements for bound light shaders. | + + +**Figure 7** : Listing of predefined variables with their binding rules. + +## References + +[1] Lucasfilm, MaterialX specification. +http://www.materialx.org, 2017. diff --git a/documents/Images/shaderx.png b/documents/Images/shaderx.png new file mode 100644 index 0000000000..17483a66d8 Binary files /dev/null and b/documents/Images/shaderx.png differ diff --git a/documents/Libraries/README.md b/documents/Libraries/README.md new file mode 100644 index 0000000000..3399ff2875 --- /dev/null +++ b/documents/Libraries/README.md @@ -0,0 +1,40 @@ +# Library Structure + +The following is the layout of the definitions and implementations provided as part of the core libraries. + +## Standard Library +- [stdlib](stdlib) + - [stdlib_defs.mtlx](stdlib/stdlib_defs.mtlx) : Single node nodedef definitions file. + - [stdlib_ng.mtlx](stdlib/stdlib_ng.mtlx) : Node graph definitions file. + - [genglsl](stdlib/genglsl): GLSL language support + - lib : Shader utility files. + - [stdlib_genglsl_impl.mtlx](stdlib/genglsl/stdlib_genglsl_impl.mtlx) : Mapping from definitions to implementations + - [stdlib_genglsl_cm_impl.mtlx](stdlib/genglsl/stdlib_genglsl_cm_impl.mtlx) : Minimal set of "default" color management implementations. + - GLSL implementation files + - [ogsfx](stdlib/genglsl/ogsfx): OGSFX support + - [stdlib_genglsl_ogsfx_impl.mtlx](stdlib/genglsl/ogsfx/stdlib_genglsl_ogsfx_impl.mtlx) : Mapping from definitions to implementations + - OGSFX implementation files + - [genosl](stdlib/genosl): OSL language support + - lib: Shader utility files. + - [stdlib_genosl_impl.mtlx](stdlib/genosl/stdlib_genosl_impl.mtlx) : Mapping from definitions to implementations + - [stdlib_genosl_cm_impl.mtlx](stdlib/genosl/stdlib_genosl_cm_impl.mtlx) : Minimal set of "default" color management implementations + - OSL implementation files. + - [osl](stdlib/genosl): OSL reference implementation + - OSL implementation files. + +## PBR library +- [pbrlib](pbrlib) + - [pbrlib_defs.mtlx](pbrlib/pbrlib_defs.mtlx) : Single node definitions file. + - [pbrlib_ng.mtlx](pbrlib/pbrlib_ng.mtlx) : Node graph definitions file. + - [genglsl](pbrlib/genglsl) : GLSL language support + - lib : Shader utility files. + - [pbrlib_genglsl_impl.mtlx](pbrlib/genglsl/pbrlib_genglsl_impl.mtlx) : Mapping from definitions to implementations + - GLSL implementation files. + - [ogsfx](pbrlib/genglsl/ogsfx) : OGSFX support + - OGSFX implementation files. + - [genosl](pbrlib/genosl) : OSL language support + - lib : Utilities + - [pbrlib_genosl_impl.mtlx](pbrlib/genosl/pbrlib_genosl_impl.mtlx) : Mapping from definitions to implementations + - OSL implementation files. + +Note that there is no reference implementation for the PBR library. diff --git a/documents/Libraries/pbrlib/genglsl/lib/mx_bsdfs.glsl b/documents/Libraries/pbrlib/genglsl/lib/mx_bsdfs.glsl index 9d5fad0967..5a5119b518 100644 --- a/documents/Libraries/pbrlib/genglsl/lib/mx_bsdfs.glsl +++ b/documents/Libraries/pbrlib/genglsl/lib/mx_bsdfs.glsl @@ -19,8 +19,27 @@ float mx_microfacet_ggx_NDF(vec3 X, vec3 Y, vec3 H, float NdotH, float alphaX, f { float XdotH = dot(X, H); float YdotH = dot(Y, H); - float denom = XdotH * XdotH / (alphaX * alphaX) + YdotH * YdotH / (alphaY * alphaY) + NdotH * NdotH; - return denom > 0.0 ? 1.0 / (M_PI * alphaX * alphaY * denom * denom) : 0.0; + float denom = mx_square(XdotH / alphaX) + mx_square(YdotH / alphaY) + mx_square(NdotH); + return 1.0 / (M_PI * alphaX * alphaY * mx_square(denom)); +} + +// https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf +// Appendix B.1 Equation 3 +float mx_microfacet_ggx_PDF(vec3 X, vec3 Y, vec3 H, float NdotH, float LdotH, float alphaX, float alphaY) +{ + return mx_microfacet_ggx_NDF(X, Y, H, NdotH, alphaX, alphaY) * NdotH / (4.0 * LdotH); +} + +// https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf +// Appendix B.2 Equation 15 +vec3 mx_microfacet_ggx_IS(vec2 Xi, vec3 X, vec3 Y, vec3 N, float alphaX, float alphaY) +{ + float phi = 2.0 * M_PI * Xi.x; + float tanTheta = sqrt(Xi.y / (1.0 - Xi.y)); + vec3 H = vec3(X * (tanTheta * alphaX * cos(phi)) + + Y * (tanTheta * alphaY * sin(phi)) + + N); + return normalize(H); } // https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf (Equation 34) diff --git a/documents/Libraries/pbrlib/genglsl/lib/mx_defines.glsl b/documents/Libraries/pbrlib/genglsl/lib/mx_defines.glsl index 414882e310..eef8fbf79d 100644 --- a/documents/Libraries/pbrlib/genglsl/lib/mx_defines.glsl +++ b/documents/Libraries/pbrlib/genglsl/lib/mx_defines.glsl @@ -1,3 +1,4 @@ #define M_PI 3.1415926535897932384626433832795 #define M_PI_INV 1.0/3.1415926535897932384626433832795 +#define M_GOLDEN_RATIO 1.6180339887498948482045868343656 #define M_FLOAT_EPS 0.000001 diff --git a/documents/Libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl b/documents/Libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl new file mode 100644 index 0000000000..2dd95c945a --- /dev/null +++ b/documents/Libraries/pbrlib/genglsl/lib/mx_environment_fis.glsl @@ -0,0 +1,83 @@ +#include "pbrlib/genglsl/lib/mx_bsdfs.glsl" + +vec2 mx_latlong_projection(vec3 dir) +{ + float latitude = -asin(dir.y) * M_PI_INV + 0.5; + latitude = clamp(latitude, 0.01, 0.99); + float longitude = atan(dir.x, -dir.z) * M_PI_INV * 0.5 + 0.5; + return vec2(longitude, latitude); +} + +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html +// Section 20.4 Equation 13 +float mx_latlong_compute_lod(vec3 dir, float pdf, float maxMipLevel, int envSamples) +{ + const float MIP_LEVEL_OFFSET = 1.5; + float effectiveMaxMipLevel = maxMipLevel - MIP_LEVEL_OFFSET; + float distortion = sqrt(1.0 - mx_square(dir.y)); + return max(effectiveMaxMipLevel - 0.5 * log2(envSamples * pdf * distortion), 0.0); +} + +vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lod, sampler2D sampler) +{ + vec3 envDir = normalize((transform * vec4(dir,0.0)).xyz); + vec2 uv = mx_latlong_projection(envDir); + return textureLod(sampler, uv, lod).rgb; +} + +// Only GGX is supported for now and the distribution argument is ignored +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, roughnessinfo roughness, int distribution) +{ + vec3 Y = normalize(cross(N, X)); + X = cross(Y, N); + + // Compute shared dot products. + float NdotV = clamp(dot(N, V), 1e-8, 1.0); + + // Integrate outgoing radiance using filtered importance sampling. + // http://cgg.mff.cuni.cz/~jaroslav/papers/2008-egsr-fis/2008-egsr-fis-final-embedded.pdf + vec3 radiance = vec3(0.0); + for (int i = 0; i < u_envSamples; i++) + { + vec2 Xi = mx_spherical_fibonacci(i, u_envSamples); + + // Compute the half vector and incoming light direction. + vec3 H = mx_microfacet_ggx_IS(Xi, X, Y, N, roughness.alphaX, roughness.alphaY); + vec3 L = -reflect(V, H); + + // Compute dot products for this sample. + float NdotH = clamp(dot(N, H), 1e-8, 1.0); + float NdotL = clamp(dot(N, L), 1e-8, 1.0); + float VdotH = clamp(dot(V, H), 1e-8, 1.0); + float LdotH = VdotH; + + // Sample the environment light from the given direction. + float pdf = mx_microfacet_ggx_PDF(X, Y, H, NdotH, LdotH, roughness.alphaX, roughness.alphaY); + float lod = mx_latlong_compute_lod(L, pdf, u_envRadianceMips - 1, u_envSamples); + vec3 sampleColor = mx_latlong_map_lookup(L, u_envMatrix, lod, u_envRadiance); + + // Compute the geometric term. + float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, roughness.alpha); + + // Fresnel is applied outside the lighting integral for now. + // TODO: Move Fresnel term into the lighting integral. + float F = 1.0; + + // Add the radiance contribution of this sample. + // From https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + // incidentLight = sampleColor * NdotL + // microfacetSpecular = D * G * F / (4 * NdotL * NdotV) + // pdf = D * NdotH / (4 * VdotH) + // radiance = incidentLight * microfacetSpecular / pdf + radiance += sampleColor * G * F * VdotH / (NdotV * NdotH); + } + + // Normalize and return the final radiance. + radiance /= float(u_envSamples); + return radiance; +} + +vec3 mx_environment_irradiance(vec3 N) +{ + return mx_latlong_map_lookup(N, u_envMatrix, 0.0, u_envIrradiance); +} diff --git a/documents/Libraries/pbrlib/genglsl/lib/mx_lighting.glsl b/documents/Libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl similarity index 77% rename from documents/Libraries/pbrlib/genglsl/lib/mx_lighting.glsl rename to documents/Libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl index 6244492568..14c9c5614a 100644 --- a/documents/Libraries/pbrlib/genglsl/lib/mx_lighting.glsl +++ b/documents/Libraries/pbrlib/genglsl/lib/mx_environment_prefilter.glsl @@ -35,14 +35,13 @@ vec3 mx_latlong_map_lookup(vec3 dir, mat4 transform, float lodBias, sampler2D sa return vec3(0.0); } -vec3 mx_environment_specular(vec3 normal, vec3 view, float roughness) +vec3 mx_environment_radiance(vec3 N, vec3 V, vec3 X, roughnessinfo roughness, int distribution) { - vec3 dir = reflect(-view, normal); - return mx_latlong_map_lookup(dir, u_envMatrix, roughness, u_envSpecular); + vec3 dir = reflect(-V, N); + return mx_latlong_map_lookup(dir, u_envMatrix, roughness.alpha, u_envRadiance); } -vec3 mx_environment_irradiance(vec3 normal) +vec3 mx_environment_irradiance(vec3 N) { - vec3 result = mx_latlong_map_lookup(normal, u_envMatrix, u_envIrradiance); - return result; + return mx_latlong_map_lookup(N, u_envMatrix, u_envIrradiance); } diff --git a/documents/Libraries/pbrlib/genglsl/lib/mx_math.glsl b/documents/Libraries/pbrlib/genglsl/lib/mx_math.glsl index 798b343cfa..5aadd579a3 100644 --- a/documents/Libraries/pbrlib/genglsl/lib/mx_math.glsl +++ b/documents/Libraries/pbrlib/genglsl/lib/mx_math.glsl @@ -42,3 +42,15 @@ bool mx_is_tiny(vec3 v) { return all(lessThan(abs(v), vec3(M_FLOAT_EPS))); } + +// https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf +float mx_golden_ratio_sequence(int i) +{ + return fract((float(i) + 1.0) * M_GOLDEN_RATIO); +} + +// https://people.irisa.fr/Ricardo.Marques/articles/2013/SF_CGF.pdf +vec2 mx_spherical_fibonacci(int i, int numSamples) +{ + return vec2((float(i) + 0.5) / float(numSamples), mx_golden_ratio_sequence(i)); +} diff --git a/documents/Libraries/pbrlib/genglsl/mx_conductorbrdf.glsl b/documents/Libraries/pbrlib/genglsl/mx_conductorbrdf.glsl index 36462a3562..c78a4ef9b7 100644 --- a/documents/Libraries/pbrlib/genglsl/mx_conductorbrdf.glsl +++ b/documents/Libraries/pbrlib/genglsl/mx_conductorbrdf.glsl @@ -1,7 +1,7 @@ #include "pbrlib/genglsl/lib/mx_bsdfs.glsl" #include "pbrlib/genglsl/lib/mx_refractionindex.glsl" -void mx_conductorbrdf_reflection(vec3 L, vec3 V, float weight, vec3 reflectivity, vec3 edgecolor, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, out BSDF result) +void mx_conductorbrdf_reflection(vec3 L, vec3 V, float weight, vec3 reflectivity, vec3 edgecolor, roughnessinfo roughness, vec3 N, vec3 X, int distribution, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -9,20 +9,20 @@ void mx_conductorbrdf_reflection(vec3 L, vec3 V, float weight, vec3 reflectivity return; } - float NdotL = dot(normal,L); - float NdotV = dot(normal,V); + float NdotL = dot(N,L); + float NdotV = dot(N,V); if (NdotL <= 0.0 || NdotV <= 0.0) { result = BSDF(0.0); return; } - vec3 bitangent = normalize(cross(normal, tangent)); + vec3 Y = normalize(cross(N, X)); vec3 H = normalize(L + V); - float NdotH = dot(normal, H); + float NdotH = dot(N, H); - float D = mx_microfacet_ggx_NDF(tangent, bitangent, H, NdotH, roughness.alphaX, roughness.alphaY); + float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.alphaX, roughness.alphaY); float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, roughness.alpha); vec3 ior_n, ior_k; @@ -36,7 +36,7 @@ void mx_conductorbrdf_reflection(vec3 L, vec3 V, float weight, vec3 reflectivity result = F * D * G / (4 * NdotV); } -void mx_conductorbrdf_indirect(vec3 V, float weight, vec3 reflectivity, vec3 edgecolor, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, out vec3 result) +void mx_conductorbrdf_indirect(vec3 V, float weight, vec3 reflectivity, vec3 edgecolor, roughnessinfo roughness, vec3 N, vec3 X, int distribution, out vec3 result) { if (weight < M_FLOAT_EPS) { @@ -47,8 +47,8 @@ void mx_conductorbrdf_indirect(vec3 V, float weight, vec3 reflectivity, vec3 edg vec3 ior_n, ior_k; mx_artistic_to_complex_ior(reflectivity, edgecolor, ior_n, ior_k); - vec3 Li = mx_environment_specular(normal, V, roughness.alpha); - vec3 F = mx_fresnel_conductor(dot(normal, V), ior_n, ior_k); + vec3 Li = mx_environment_radiance(N, V, X, roughness, distribution); + vec3 F = mx_fresnel_conductor(dot(N, V), ior_n, ior_k); F *= weight; result = Li * F; } diff --git a/documents/Libraries/pbrlib/genglsl/mx_dielectricbrdf.glsl b/documents/Libraries/pbrlib/genglsl/mx_dielectricbrdf.glsl index 9a20e146e8..82418c7485 100644 --- a/documents/Libraries/pbrlib/genglsl/mx_dielectricbrdf.glsl +++ b/documents/Libraries/pbrlib/genglsl/mx_dielectricbrdf.glsl @@ -1,6 +1,6 @@ #include "pbrlib/genglsl/lib/mx_bsdfs.glsl" -void mx_dielectricbrdf_reflection(vec3 L, vec3 V, float weight, vec3 tint, float ior, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, BSDF base, out BSDF result) +void mx_dielectricbrdf_reflection(vec3 L, vec3 V, float weight, vec3 tint, float ior, roughnessinfo roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -8,20 +8,20 @@ void mx_dielectricbrdf_reflection(vec3 L, vec3 V, float weight, vec3 tint, float return; } - float NdotL = dot(normal,L); - float NdotV = dot(normal,V); + float NdotL = dot(N,L); + float NdotV = dot(N,V); if (NdotL <= 0.0 || NdotV <= 0.0) { result = base; return; } - vec3 bitangent = normalize(cross(normal, tangent)); + vec3 Y = normalize(cross(N, X)); vec3 H = normalize(L + V); - float NdotH = dot(normal, H); + float NdotH = dot(N, H); - float D = mx_microfacet_ggx_NDF(tangent, bitangent, H, NdotH, roughness.alphaX, roughness.alphaY); + float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.alphaX, roughness.alphaY); float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, roughness.alpha); float VdotH = dot(V, H); @@ -33,7 +33,7 @@ void mx_dielectricbrdf_reflection(vec3 L, vec3 V, float weight, vec3 tint, float + base * (1.0 - F); // Base layer reflection attenuated by top fresnel } -void mx_dielectricbrdf_transmission(vec3 V, float weight, vec3 tint, float ior, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, BSDF base, out BSDF result) +void mx_dielectricbrdf_transmission(vec3 V, float weight, vec3 tint, float ior, roughnessinfo roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -46,14 +46,14 @@ void mx_dielectricbrdf_transmission(vec3 V, float weight, vec3 tint, float ior, // inverse of top layer reflectance. // Abs here to allow transparency through backfaces - float NdotV = abs(dot(normal,V)); + float NdotV = abs(dot(N,V)); float F = mx_fresnel_schlick(NdotV, ior); F *= weight; result = base * (1.0 - F); // Base layer transmission attenuated by top fresnel } -void mx_dielectricbrdf_indirect(vec3 V, float weight, vec3 tint, float ior, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, BSDF base, out BSDF result) +void mx_dielectricbrdf_indirect(vec3 V, float weight, vec3 tint, float ior, roughnessinfo roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -61,9 +61,9 @@ void mx_dielectricbrdf_indirect(vec3 V, float weight, vec3 tint, float ior, roug return; } - vec3 Li = mx_environment_specular(normal, V, roughness.alpha); + vec3 Li = mx_environment_radiance(N, V, X, roughness, distribution); - float NdotV = dot(normal,V); + float NdotV = dot(N,V); float F = mx_fresnel_schlick_roughness(NdotV, ior, roughness.alpha); F *= weight; diff --git a/documents/Libraries/pbrlib/genglsl/mx_generalizedschlickbrdf.glsl b/documents/Libraries/pbrlib/genglsl/mx_generalizedschlickbrdf.glsl index 4e940143f7..a4e539a968 100644 --- a/documents/Libraries/pbrlib/genglsl/mx_generalizedschlickbrdf.glsl +++ b/documents/Libraries/pbrlib/genglsl/mx_generalizedschlickbrdf.glsl @@ -1,6 +1,6 @@ #include "pbrlib/genglsl/lib/mx_bsdfs.glsl" -void mx_generalizedschlickbrdf_reflection(vec3 L, vec3 V, float weight, vec3 color0, vec3 color90, float exponent, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, BSDF base, out BSDF result) +void mx_generalizedschlickbrdf_reflection(vec3 L, vec3 V, float weight, vec3 color0, vec3 color90, float exponent, roughnessinfo roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -8,20 +8,20 @@ void mx_generalizedschlickbrdf_reflection(vec3 L, vec3 V, float weight, vec3 col return; } - float NdotL = dot(normal,L); - float NdotV = dot(normal,V); + float NdotL = dot(N,L); + float NdotV = dot(N,V); if (NdotL <= 0.0 || NdotV <= 0.0) { result = base; return; } - vec3 bitangent = normalize(cross(normal, tangent)); + vec3 Y = normalize(cross(N, X)); vec3 H = normalize(L + V); - float NdotH = dot(normal, H); + float NdotH = dot(N, H); - float D = mx_microfacet_ggx_NDF(tangent, bitangent, H, NdotH, roughness.alphaX, roughness.alphaY); + float D = mx_microfacet_ggx_NDF(X, Y, H, NdotH, roughness.alphaX, roughness.alphaY); float G = mx_microfacet_ggx_smith_G(NdotL, NdotV, roughness.alpha); float VdotH = dot(V, H); @@ -34,7 +34,7 @@ void mx_generalizedschlickbrdf_reflection(vec3 L, vec3 V, float weight, vec3 col + base * (1.0 - avgF); // Base layer reflection attenuated by top fresnel } -void mx_generalizedschlickbrdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, BSDF base, out BSDF result) +void mx_generalizedschlickbrdf_transmission(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, roughnessinfo roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -47,7 +47,7 @@ void mx_generalizedschlickbrdf_transmission(vec3 V, float weight, vec3 color0, v // inverse of top layer reflectance. // Abs here to allow transparency through backfaces - float NdotV = abs(dot(normal,V)); + float NdotV = abs(dot(N,V)); vec3 F = mx_fresnel_schlick(NdotV, color0, color90, exponent); F *= weight; float avgF = dot(F, vec3(1.0 / 3.0)); @@ -55,7 +55,7 @@ void mx_generalizedschlickbrdf_transmission(vec3 V, float weight, vec3 color0, v result = base * (1.0 - avgF); // Base layer transmission attenuated by top fresnel } -void mx_generalizedschlickbrdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, roughnessinfo roughness, vec3 normal, vec3 tangent, int distribution, BSDF base, out BSDF result) +void mx_generalizedschlickbrdf_indirect(vec3 V, float weight, vec3 color0, vec3 color90, float exponent, roughnessinfo roughness, vec3 N, vec3 X, int distribution, BSDF base, out BSDF result) { if (weight < M_FLOAT_EPS) { @@ -63,9 +63,9 @@ void mx_generalizedschlickbrdf_indirect(vec3 V, float weight, vec3 color0, vec3 return; } - vec3 Li = mx_environment_specular(normal, V, roughness.alpha); + vec3 Li = mx_environment_radiance(N, V, X, roughness, distribution); - float NdotV = dot(normal,V); + float NdotV = dot(N,V); vec3 F = mx_fresnel_schlick(NdotV, color0, color90, exponent); F *= weight; float avgF = dot(F, vec3(1.0 / 3.0)); diff --git a/documents/Libraries/pbrlib/genglsl/mx_sheenbrdf.glsl b/documents/Libraries/pbrlib/genglsl/mx_sheenbrdf.glsl index cc9af52a35..85a770de2d 100644 --- a/documents/Libraries/pbrlib/genglsl/mx_sheenbrdf.glsl +++ b/documents/Libraries/pbrlib/genglsl/mx_sheenbrdf.glsl @@ -1,7 +1,7 @@ #include "pbrlib/genglsl/lib/mx_bsdfs.glsl" // Fake with simple diffuse reflection for now -void mx_sheenbrdf_reflection(vec3 L, vec3 V, float weight, vec3 color, float roughness, vec3 normal, out BSDF result) +void mx_sheenbrdf_reflection(vec3 L, vec3 V, float weight, vec3 color, float roughness, vec3 normal, BSDF base, out BSDF result) { float NdotL = dot(L, normal); if (NdotL <= 0.0 || weight < M_FLOAT_EPS) @@ -18,7 +18,7 @@ void mx_sheenbrdf_reflection(vec3 L, vec3 V, float weight, vec3 color, float rou } // Fake with simple diffuse reflection for now -void mx_sheenbrdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, out vec3 result) +void mx_sheenbrdf_indirect(vec3 V, float weight, vec3 color, float roughness, vec3 normal, BSDF base, out vec3 result) { if (weight < M_FLOAT_EPS) { diff --git a/documents/Libraries/pbrlib/genglsl/mx_subsurfacebrdf.glsl b/documents/Libraries/pbrlib/genglsl/mx_subsurfacebrdf.glsl index 811471c2cf..88cc436e39 100644 --- a/documents/Libraries/pbrlib/genglsl/mx_subsurfacebrdf.glsl +++ b/documents/Libraries/pbrlib/genglsl/mx_subsurfacebrdf.glsl @@ -13,7 +13,7 @@ void mx_subsurfacebrdf_reflection(vec3 L, vec3 V, float weight, vec3 color, vec3 } // Fake with simple diffuse transmission -void mx_subsurfacebrdf_transmission(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, out vec3 result) +void mx_subsurfacebrdf_indirect(vec3 V, float weight, vec3 color, vec3 radius, float anisotropy, vec3 normal, out vec3 result) { if (weight < M_FLOAT_EPS) { diff --git a/documents/Libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx b/documents/Libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx index d9b74593d3..a2f59f5a8e 100644 --- a/documents/Libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx +++ b/documents/Libraries/pbrlib/genglsl/pbrlib_genglsl_impl.mtlx @@ -37,9 +37,6 @@ - - - diff --git a/documents/Libraries/pbrlib/genosl/mx_mixsurface.osl b/documents/Libraries/pbrlib/genosl/mx_mixsurface.osl index 8f04cc9971..60ab0d9034 100644 --- a/documents/Libraries/pbrlib/genosl/mx_mixsurface.osl +++ b/documents/Libraries/pbrlib/genosl/mx_mixsurface.osl @@ -1,4 +1,4 @@ -void mx_layeredsurface(surfaceshader in1, surfaceshader in2, float weight, output surfaceshader result) +void mx_mixsurface(surfaceshader in1, surfaceshader in2, float weight, output surfaceshader result) { float w = clamp(weight, 0.0, 1.0); result = in1 * (1.0 - w) + in2 * w; diff --git a/documents/Libraries/pbrlib/genosl/mx_sheenbrdf.osl b/documents/Libraries/pbrlib/genosl/mx_sheenbrdf.osl index 85bf63b170..af6fc7df93 100644 --- a/documents/Libraries/pbrlib/genosl/mx_sheenbrdf.osl +++ b/documents/Libraries/pbrlib/genosl/mx_sheenbrdf.osl @@ -1,5 +1,5 @@ -void mx_sheenbrdf(float weight, color _color, float rougness, vector _normal, BSDF base, output BSDF result) +void mx_sheenbrdf(float weight, color _color, float roughness, vector _normal, BSDF base, output BSDF result) { // TODO: implement this properly - result = _color * weight * oren_nayar({{normal}}, {{roughness}}) + result = _color * weight * oren_nayar(_normal, roughness); } diff --git a/documents/Libraries/stdlib/genglsl/lib/mx_get_target_uv_noop.glsl b/documents/Libraries/stdlib/genglsl/lib/mx_get_target_uv_noop.glsl new file mode 100644 index 0000000000..ee3d78693d --- /dev/null +++ b/documents/Libraries/stdlib/genglsl/lib/mx_get_target_uv_noop.glsl @@ -0,0 +1,7 @@ +// +// Function to transform uv-coordinates before texture sampling +// +vec2 mx_get_target_uv(vec2 uv) +{ + return uv; +} diff --git a/documents/Libraries/stdlib/genglsl/lib/mx_get_target_uv_vflip.glsl b/documents/Libraries/stdlib/genglsl/lib/mx_get_target_uv_vflip.glsl new file mode 100644 index 0000000000..62c1a70e79 --- /dev/null +++ b/documents/Libraries/stdlib/genglsl/lib/mx_get_target_uv_vflip.glsl @@ -0,0 +1,7 @@ +// +// Function to transform uv-coordinates before texture sampling +// +vec2 mx_get_target_uv(vec2 uv) +{ + return vec2(uv.x, 1.0 - uv.y); +} diff --git a/documents/Libraries/stdlib/genglsl/mx_image_color2.glsl b/documents/Libraries/stdlib/genglsl/mx_image_color2.glsl index f67c11f62c..1b2110e0ed 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_color2.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_color2.glsl @@ -3,7 +3,8 @@ void mx_image_color2(sampler2D tex_sampler, int layer, vec2 defaultval, vec2 tex // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord).rg; + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv).rg; } else { diff --git a/documents/Libraries/stdlib/genglsl/mx_image_color3.glsl b/documents/Libraries/stdlib/genglsl/mx_image_color3.glsl index 86cff0384a..0a1d930b33 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_color3.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_color3.glsl @@ -3,7 +3,8 @@ void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 tex // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord).rgb; + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv).rgb; } else { diff --git a/documents/Libraries/stdlib/genglsl/mx_image_color4.glsl b/documents/Libraries/stdlib/genglsl/mx_image_color4.glsl index 62aeae5d2d..1c831928b6 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_color4.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_color4.glsl @@ -3,7 +3,8 @@ void mx_image_color4(sampler2D tex_sampler, int layer, vec4 defaultval, vec2 tex // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord); + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv); } else { diff --git a/documents/Libraries/stdlib/genglsl/mx_image_float.glsl b/documents/Libraries/stdlib/genglsl/mx_image_float.glsl index 527ef48ef4..b673eac53e 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_float.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_float.glsl @@ -3,7 +3,8 @@ void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 tex // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord).r; + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv).r; } else { diff --git a/documents/Libraries/stdlib/genglsl/mx_image_vector2.glsl b/documents/Libraries/stdlib/genglsl/mx_image_vector2.glsl index a4de6fd78b..9d6cd069f7 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_vector2.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_vector2.glsl @@ -3,7 +3,8 @@ void mx_image_vector2(sampler2D tex_sampler, int layer, vec2 defaultval, vec2 te // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord).rg; + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv).rg; } else { diff --git a/documents/Libraries/stdlib/genglsl/mx_image_vector3.glsl b/documents/Libraries/stdlib/genglsl/mx_image_vector3.glsl index 86ae2a6f9b..3d0c89cf69 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_vector3.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_vector3.glsl @@ -3,7 +3,8 @@ void mx_image_vector3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 te // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord).rgb; + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv).rgb; } else { diff --git a/documents/Libraries/stdlib/genglsl/mx_image_vector4.glsl b/documents/Libraries/stdlib/genglsl/mx_image_vector4.glsl index 0d3dd68b06..1070388f18 100644 --- a/documents/Libraries/stdlib/genglsl/mx_image_vector4.glsl +++ b/documents/Libraries/stdlib/genglsl/mx_image_vector4.glsl @@ -3,7 +3,8 @@ void mx_image_vector4(sampler2D tex_sampler, int layer, vec4 defaultval, vec2 te // TODO: Fix handling of addressmode if(textureSize(tex_sampler, 0).x > 1) { - result = texture(tex_sampler, texcoord); + vec2 uv = mx_get_target_uv(texcoord); + result = texture(tex_sampler, uv); } else { diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color2.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color2.glsl deleted file mode 100644 index a03fb2a5bd..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color2.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_color2(sampler2D tex_sampler, int layer, vec2 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out vec2 result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord).rg; - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color3.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color3.glsl deleted file mode 100644 index d782020b37..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color3.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_color3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out vec3 result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord).rgb; - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color4.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color4.glsl deleted file mode 100644 index 38dd7cbe24..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_color4.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_color4(sampler2D tex_sampler, int layer, vec4 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out vec4 result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord); - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_float.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_float.glsl deleted file mode 100644 index dd992d1b9c..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_float.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_float(sampler2D tex_sampler, int layer, float defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out float result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord).r; - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector2.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector2.glsl deleted file mode 100644 index 3ede5a43e9..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector2.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_vector2(sampler2D tex_sampler, int layer, vec2 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out vec2 result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord).rg; - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector3.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector3.glsl deleted file mode 100644 index 0827696275..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector3.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_vector3(sampler2D tex_sampler, int layer, vec3 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out vec3 result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord).rgb; - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector4.glsl b/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector4.glsl deleted file mode 100644 index c5a186edac..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/mx_image_vector4.glsl +++ /dev/null @@ -1,14 +0,0 @@ -void mx_image_vector4(sampler2D tex_sampler, int layer, vec4 defaultval, vec2 texcoord, int uaddressmode, int vaddressmode, int filtertype, int framerange, int frameoffset, int frameendaction, out vec4 result) -{ - // TODO: Fix handling of addressmode - if(textureSize(tex_sampler, 0).x > 1) - { - // Flip v-component to match MaterialX convention - texcoord.y = 1.0 - texcoord.y; - result = texture(tex_sampler, texcoord); - } - else - { - result = defaultval; - } -} diff --git a/documents/Libraries/stdlib/genglsl/ogsfx/stdlib_genglsl_ogsfx_impl.mtlx b/documents/Libraries/stdlib/genglsl/ogsfx/stdlib_genglsl_ogsfx_impl.mtlx deleted file mode 100644 index 879e7fa208..0000000000 --- a/documents/Libraries/stdlib/genglsl/ogsfx/stdlib_genglsl_ogsfx_impl.mtlx +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/documents/Libraries/stdlib/genglsl/cm_genglsl_impl.mtlx b/documents/Libraries/stdlib/genglsl/stdlib_genglsl_cm_impl.mtlx similarity index 100% rename from documents/Libraries/stdlib/genglsl/cm_genglsl_impl.mtlx rename to documents/Libraries/stdlib/genglsl/stdlib_genglsl_cm_impl.mtlx diff --git a/documents/Libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx b/documents/Libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx index ab01ac2f7e..839128851f 100644 --- a/documents/Libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx +++ b/documents/Libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx @@ -13,25 +13,25 @@ - + - + - + - + - + - + - + diff --git a/documents/Libraries/stdlib/genosl/lib/mx_get_target_uv_noop.osl b/documents/Libraries/stdlib/genosl/lib/mx_get_target_uv_noop.osl new file mode 100644 index 0000000000..1f8b4d8999 --- /dev/null +++ b/documents/Libraries/stdlib/genosl/lib/mx_get_target_uv_noop.osl @@ -0,0 +1,7 @@ +// +// Function to transform texture coordinates before texture sampling +// +vector2 mx_get_target_uv(vector2 texcoord) +{ + return texcoord; +} diff --git a/documents/Libraries/stdlib/genosl/lib/mx_get_target_uv_vflip.osl b/documents/Libraries/stdlib/genosl/lib/mx_get_target_uv_vflip.osl new file mode 100644 index 0000000000..efd536b1e6 --- /dev/null +++ b/documents/Libraries/stdlib/genosl/lib/mx_get_target_uv_vflip.osl @@ -0,0 +1,7 @@ +// +// Function to transform texture coordinates before texture sampling +// +vec2 mx_get_target_uv(vec2 texcoord) +{ + return vec2(texcoord.x, 1.0 - texcoord.y); +} diff --git a/documents/Libraries/stdlib/genosl/mx_image_color2.osl b/documents/Libraries/stdlib/genosl/mx_image_color2.osl index 876142c3d8..0c8332c0ff 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_color2.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_color2.osl @@ -7,7 +7,8 @@ void mx_image_color2(string file, string layer, color2 default_value, vector2 te } color missingColor = color(default_value.r, default_value.a, 0.0); - color rgb = texture(file, texcoord.x, 1.0 - texcoord.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); + vector2 st = mx_get_target_uv(texcoord); + color rgb = texture(file, st.x, st.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); out.r = rgb[0]; out.a = rgb[1]; diff --git a/documents/Libraries/stdlib/genosl/mx_image_color3.osl b/documents/Libraries/stdlib/genosl/mx_image_color3.osl index 6adf2e8f0d..4cf427af8e 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_color3.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_color3.osl @@ -7,5 +7,6 @@ void mx_image_color3(string file, string layer, color default_value, vector2 tex } color missingColor = default_value; - out = texture(file, texcoord.x, 1.0 - texcoord.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); + vector2 st = mx_get_target_uv(texcoord); + out = texture(file, st.x, st.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); } diff --git a/documents/Libraries/stdlib/genosl/mx_image_color4.osl b/documents/Libraries/stdlib/genosl/mx_image_color4.osl index 844aba4fc6..23ede2674b 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_color4.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_color4.osl @@ -8,9 +8,9 @@ void mx_image_color4(string file, string layer, color4 default_value, vector2 te color missingColor = default_value.rgb; float missingAlpha = default_value.a; - + vector2 st = mx_get_target_uv(texcoord); float alpha; - color rgb = texture(file, texcoord.x, 1.0 - texcoord.y, "alpha", alpha, "subimage", layer, + color rgb = texture(file, st.x, st.y, "alpha", alpha, "subimage", layer, "missingcolor", missingColor, "missingalpha", missingAlpha, "wrap", uaddressmode); out = color4(rgb, alpha); diff --git a/documents/Libraries/stdlib/genosl/mx_image_float.osl b/documents/Libraries/stdlib/genosl/mx_image_float.osl index f72471f0a2..0908c5b8de 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_float.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_float.osl @@ -7,6 +7,7 @@ void mx_image_float(string file, string layer, float default_value, vector2 texc } color missingColor = color(default_value); - color rgb = texture(file, texcoord.x, 1.0 - texcoord.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); + vector2 st = mx_get_target_uv(texcoord); + color rgb = texture(file, st.x, st.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); out = rgb[0]; } diff --git a/documents/Libraries/stdlib/genosl/mx_image_vector2.osl b/documents/Libraries/stdlib/genosl/mx_image_vector2.osl index fe2eb4a318..f27c99c6ee 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_vector2.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_vector2.osl @@ -7,7 +7,8 @@ void mx_image_vector2(string file, string layer, vector2 default_value, vector2 } color missingColor = color(default_value.x, default_value.y, 0.0); - color rgb = texture(file, texcoord.x, 1.0 - texcoord.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); + vector2 st = mx_get_target_uv(texcoord); + color rgb = texture(file, st.x, st.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); out.x = rgb[0]; out.y = rgb[1]; } diff --git a/documents/Libraries/stdlib/genosl/mx_image_vector3.osl b/documents/Libraries/stdlib/genosl/mx_image_vector3.osl index 4d33fa1bf7..648678fb7a 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_vector3.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_vector3.osl @@ -7,5 +7,6 @@ void mx_image_vector3(string file, string layer, vector default_value, vector2 t } color missingColor = default_value; - out = texture(file, texcoord.x, 1.0 - texcoord.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); + vector2 st = mx_get_target_uv(texcoord); + out = texture(file, st.x, st.y, "subimage", layer, "missingcolor", missingColor, "wrap", uaddressmode); } diff --git a/documents/Libraries/stdlib/genosl/mx_image_vector4.osl b/documents/Libraries/stdlib/genosl/mx_image_vector4.osl index 36de9c9b92..7bb3d16df5 100644 --- a/documents/Libraries/stdlib/genosl/mx_image_vector4.osl +++ b/documents/Libraries/stdlib/genosl/mx_image_vector4.osl @@ -8,9 +8,9 @@ void mx_image_vector4(string file, string layer, vector4 default_value, vector2 color missingColor = color(default_value.x, default_value.y, default_value.z); float missingAlpha = default_value.w; - + vector2 st = mx_get_target_uv(texcoord); float alpha; - color rgb = texture(file, texcoord.x, 1.0 - texcoord.y, "alpha", alpha, "subimage", layer, + color rgb = texture(file, st.x, st.y, "alpha", alpha, "subimage", layer, "missingcolor", missingColor, "missingalpha", missingAlpha, "wrap", uaddressmode); out = vector4(rgb[0], rgb[1], rgb[2], alpha); diff --git a/documents/Libraries/stdlib/genosl/cm_genosl_impl.mtlx b/documents/Libraries/stdlib/genosl/stdlib_genosl_cm_impl.mtlx similarity index 100% rename from documents/Libraries/stdlib/genosl/cm_genosl_impl.mtlx rename to documents/Libraries/stdlib/genosl/stdlib_genosl_cm_impl.mtlx diff --git a/documents/Libraries/stdlib/osl/README.md b/documents/Libraries/stdlib/osl/README.md index 53fc4ebb91..74d70bf87c 100644 --- a/documents/Libraries/stdlib/osl/README.md +++ b/documents/Libraries/stdlib/osl/README.md @@ -1,4 +1,4 @@ -# OSL Source +# OSL Reference Implementations This folder contains reference OSL implementations for the standard MaterialX nodes, documenting their intended functionality as of MaterialX v1.35. diff --git a/documents/Libraries/stdlib/osl/stdlib_osl_impl.mtlx b/documents/Libraries/stdlib/osl/stdlib_osl_impl.mtlx index 5e9ab84fc5..05967355d1 100644 --- a/documents/Libraries/stdlib/osl/stdlib_osl_impl.mtlx +++ b/documents/Libraries/stdlib/osl/stdlib_osl_impl.mtlx @@ -1,5 +1,5 @@ - - - - + + + - + @@ -24,6 +51,7 @@ - + + diff --git a/documents/TestSuite/_options.mtlx b/documents/TestSuite/_options.mtlx index 13c0594bda..ef7d0fa518 100644 --- a/documents/TestSuite/_options.mtlx +++ b/documents/TestSuite/_options.mtlx @@ -24,10 +24,10 @@ - + @@ -64,5 +64,11 @@ + + + + + + diff --git a/documents/TestSuite/pbrlib/materials/default_material.mtlx b/documents/TestSuite/pbrlib/materials/default_material.mtlx new file mode 100644 index 0000000000..d288a24471 --- /dev/null +++ b/documents/TestSuite/pbrlib/materials/default_material.mtlx @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documents/TestSuite/pbrlib/materials/shader_ops.mtlx b/documents/TestSuite/pbrlib/materials/shader_ops.mtlx new file mode 100644 index 0000000000..98c4e0b510 --- /dev/null +++ b/documents/TestSuite/pbrlib/materials/shader_ops.mtlx @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documents/TestSuite/pbrlib/materials/surface_ops.mtlx b/documents/TestSuite/pbrlib/materials/surface_ops.mtlx new file mode 100644 index 0000000000..c7c85f7050 --- /dev/null +++ b/documents/TestSuite/pbrlib/materials/surface_ops.mtlx @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documents/TestSuite/stdlib/application/viewdirection.mtlx b/documents/TestSuite/stdlib/application/viewdirection.mtlx new file mode 100644 index 0000000000..e703375767 --- /dev/null +++ b/documents/TestSuite/stdlib/application/viewdirection.mtlx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/documents/TestSuite/stdlib/math/transform.mtlx b/documents/TestSuite/stdlib/math/transform.mtlx index 36aa3ffce7..0622a11b75 100644 --- a/documents/TestSuite/stdlib/math/transform.mtlx +++ b/documents/TestSuite/stdlib/math/transform.mtlx @@ -67,6 +67,17 @@ Basic transform function tests each test is in a separate graph for each variati --> + + + + + + + + + + + @@ -107,4 +118,3 @@ Basic transform function tests each test is in a separate graph for each variati - diff --git a/python/MaterialXTest/shaderx.py b/python/MaterialXTest/shaderx.py new file mode 100644 index 0000000000..fb4118b679 --- /dev/null +++ b/python/MaterialXTest/shaderx.py @@ -0,0 +1,118 @@ +import math +import os +import unittest + +import MaterialX as mx + +_fileDir = os.path.dirname(os.path.abspath(__file__)) + +def _getSubDirectories(libraryPath): + return [name for name in os.listdir(libraryPath) + if os.path.isdir(os.path.join(libraryPath, name))] + +def _getMTLXFilesInDirectory(path): + for file in os.listdir(path): + if file.endswith(".mtlx"): + yield file + +def _loadLibrary(file, doc): + libDoc = mx.createDocument() + mx.readFromXmlFile(libDoc, file) + libDoc.setSourceUri(file) + copyOptions = mx.CopyOptions() + copyOptions.skipDuplicateElements = True; + doc.importLibrary(libDoc, copyOptions) + +def _loadLibraries(doc, searchPath, libraryPath): + librarySubPaths = _getSubDirectories(libraryPath) + librarySubPaths.append(libraryPath) + for path in librarySubPaths: + filenames = _getMTLXFilesInDirectory(os.path.join(libraryPath, path)) + for filename in filenames: + filePath = os.path.join(libraryPath, os.path.join(path, filename)) + _loadLibrary(filePath, doc) + +""" +Unit tests for ShaderX Python. +""" +class TestShaderX(unittest.TestCase): + + def test_ShaderInterface(self): + doc = mx.createDocument() + + searchPath = os.path.join(_fileDir, "../../documents/Libraries") + libraryPath = os.path.join(searchPath, "stdlib") + _loadLibraries(doc, searchPath, libraryPath) + + exampleName = u"shader_interface" + + # Create a nodedef taking three color3 and producing another color3 + nodeDef = doc.addNodeDef("ND_foo", "color3", "foo") + fooInputA = nodeDef.addInput("a", "color3") + fooInputB = nodeDef.addInput("b", "color3") + fooOutput = nodeDef.addOutput("o", "color3") + fooInputA.setValue(mx.Color3(1.0, 1.0, 0.0)) + fooInputB.setValue(mx.Color3(0.8, 0.1, 0.1)) + + # Create an implementation graph for the nodedef performing + # a multiplication of the three colors. + nodeGraph = doc.addNodeGraph("IMP_foo") + nodeGraph.setAttribute("nodedef", nodeDef.getName()) + + output = nodeGraph.addOutput(fooOutput.getName(), "color3") + mult1 = nodeGraph.addNode("multiply", "mult1", "color3") + in1 = mult1.addInput("in1", "color3") + in1.setInterfaceName(fooInputA.getName()) + in2 = mult1.addInput("in2", "color3") + in2.setInterfaceName(fooInputB.getName()) + output.setConnectedNode(mult1) + + foo = doc.addNode("foo", "foo1", "color3") + output = doc.addOutput("foo_test", "color3"); + output.setNodeName("foo1"); + output.setAttribute("output", "o"); + + options = mx.GenOptions() + + shadergen = mx.ArnoldShaderGenerator.create() + # Add path to find all source code snippets + shadergen.registerSourceCodeSearchPath(mx.FilePath(searchPath)) + # Add path to find OSL include files + shadergen.registerSourceCodeSearchPath(mx.FilePath(os.path.join(searchPath, "stdlib/osl"))) + + # Test complete mode + options.shaderInterfaceType = mx.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE; + shader = shadergen.generate(exampleName, output, options); + self.assertTrue(shader) + self.assertTrue(len(shader.getSourceCode(mx.Shader.PIXEL_STAGE)) > 0) + + uniforms = shader.getUniformBlock(mx.Shader.PIXEL_STAGE, mx.Shader.PUBLIC_UNIFORMS) + self.assertTrue(uniforms.size() == 2) + + outputs = shader.getOutputBlock() + self.assertTrue(outputs.size() == 1) + self.assertTrue(outputs[0].name == output.getName()) + + file = open(shader.getName() + "_complete.osl", "w+") + file.write(shader.getSourceCode(mx.Shader.PIXEL_STAGE)) + file.close() + + # Test reduced mode + options.shaderInterfaceType = mx.ShaderInterfaceType.SHADER_INTERFACE_REDUCED; + shader = shadergen.generate(exampleName, output, options); + self.assertTrue(shader) + self.assertTrue(len(shader.getSourceCode(mx.Shader.PIXEL_STAGE)) > 0) + + uniforms = shader.getUniformBlock(mx.Shader.PIXEL_STAGE, mx.Shader.PUBLIC_UNIFORMS) + self.assertTrue(uniforms.size() == 0) + + outputs = shader.getOutputBlock() + self.assertTrue(outputs.size() == 1) + self.assertTrue(outputs[0].name == output.getName()) + + file = open(shader.getName() + "_reduced.osl", "w+") + file.write(shader.getSourceCode(mx.Shader.PIXEL_STAGE)) + file.close() + +if __name__ == '__main__': + unittest.main() diff --git a/source/MaterialXCore/Document.cpp b/source/MaterialXCore/Document.cpp index 1e60ec6fd6..8aa8dc7117 100644 --- a/source/MaterialXCore/Document.cpp +++ b/source/MaterialXCore/Document.cpp @@ -206,6 +206,24 @@ vector Document::getMatchingPorts(const string& nodeName) const return ports; } +ValuePtr Document::getGeomAttrValue(const string& geomAttrName, const string& geom) const +{ + ValuePtr value; + for (GeomInfoPtr geomInfo : getGeomInfos()) + { + if (!geomStringsMatch(geom, geomInfo->getActiveGeom())) + { + continue; + } + GeomAttrPtr geomAttr = geomInfo->getGeomAttr(geomAttrName); + if (geomAttr) + { + value = geomAttr->getValue(); + } + } + return value; +} + vector Document::getMatchingNodeDefs(const string& nodeName) const { // Refresh the cache. @@ -428,6 +446,38 @@ void Document::upgradeVersion() } } + // Combine udim assignments into udim sets. + if (getGeomAttrValue("udim") && !getGeomAttrValue("udimset")) + { + StringSet udimSet; + for (GeomInfoPtr geomInfo : getGeomInfos()) + { + for (GeomAttrPtr geomAttr : geomInfo->getGeomAttrs()) + { + if (geomAttr->getName() == "udim") + { + udimSet.insert(geomAttr->getValueString()); + } + } + } + + std::string udimSetString; + for (const std::string& udim : udimSet) + { + if (udimSetString.empty()) + { + udimSetString = udim; + } + else + { + udimSetString += ", " + udim; + } + } + + GeomInfoPtr udimSetInfo = addGeomInfo(); + udimSetInfo->setGeomAttrValue("udimset", udimSetString, getTypeString()); + } + minorVersion = 34; } diff --git a/source/MaterialXCore/Document.h b/source/MaterialXCore/Document.h index 1aca50e773..9e710c7544 100644 --- a/source/MaterialXCore/Document.h +++ b/source/MaterialXCore/Document.h @@ -169,6 +169,9 @@ class Document : public GraphElement removeChildOfType(name); } + /// Return the value of a geometric attribute for the given geometry string. + ValuePtr getGeomAttrValue(const string& geomAttrName, const string& geom = UNIVERSAL_GEOM_NAME) const; + /// @} /// @name GeomPropDef Elements /// @{ @@ -200,7 +203,7 @@ class Document : public GraphElement void removeGeomPropDef(const string& name) { removeChildOfType(name); - } + } /// @} /// @name Look Elements diff --git a/source/MaterialXCore/Element.cpp b/source/MaterialXCore/Element.cpp index 1c14589b73..e5eea28254 100644 --- a/source/MaterialXCore/Element.cpp +++ b/source/MaterialXCore/Element.cpp @@ -525,6 +525,10 @@ TypeDefPtr TypedElement::getTypeDef() const string ValueElement::getResolvedValueString(StringResolverPtr resolver) const { + if (!StringResolver::isResolvedType(getType())) + { + return getValueString(); + } if (!resolver) { resolver = createStringResolver(); diff --git a/source/MaterialXCore/Element.h b/source/MaterialXCore/Element.h index 0dde619a89..f15ec99440 100644 --- a/source/MaterialXCore/Element.h +++ b/source/MaterialXCore/Element.h @@ -1037,6 +1037,21 @@ class ValueElement : public TypedElement return Value::createValueFromStrings(getValueString(), getType()); } + /// Return the resolved value of an element as a generic value object, which + /// may be queried to access its data. + /// + /// @param resolver An optional string resolver, which will be used to + /// apply string substitutions. By default, a new string resolver + /// will be created at this scope and applied to the return value. + /// @return A shared pointer to the typed value of this element, or an + /// empty shared pointer if no value is present. + ValuePtr getResolvedValue(StringResolverPtr resolver = nullptr) const + { + if (!hasValue()) + return ValuePtr(); + return Value::createValueFromStrings(getResolvedValueString(resolver), getType()); + } + /// @} /// @name Bound Value /// @{ @@ -1080,8 +1095,8 @@ class ValueElement : public TypedElement static const string PUBLIC_NAME_ATTRIBUTE; static const string INTERFACE_NAME_ATTRIBUTE; static const string IMPLEMENTATION_NAME_ATTRIBUTE; - static const string ENUM_ATTRIBUTE; static const string IMPLEMENTATION_TYPE_ATTRIBUTE; + static const string ENUM_ATTRIBUTE; static const string ENUM_VALUES_ATTRIBUTE; static const string UI_NAME_ATTRIBUTE; static const string UI_FOLDER_ATTRIBUTE; @@ -1235,6 +1250,12 @@ class StringResolver /// return the resulting string. virtual string resolve(const string& str, const string& type) const; + /// Return true if the given type may be resolved by this class. + static bool isResolvedType(const string& type) + { + return type == FILENAME_TYPE_STRING || type == GEOMNAME_TYPE_STRING; + } + /// @} protected: diff --git a/source/MaterialXCore/Geom.h b/source/MaterialXCore/Geom.h index 9e7b82cd4d..73143b7134 100644 --- a/source/MaterialXCore/Geom.h +++ b/source/MaterialXCore/Geom.h @@ -350,13 +350,15 @@ class GeomAttr : public ValueElement /// @class GeomPropDef /// An element representing a declaration of geometric input data. -/// Can be used to declare a geometric property with specific modifiers set, -/// and assing this a name to be referenced elsewhere in a document. -/// For example assigning the name "Nworld" to world space normal, or the name -/// "UV1" for texcoords of set index 1. The geometric property is defined -/// by a geometric node and modifiers on this node. -/// Once a GeomPropDef has been defined it can be referenced by defaultgeomprop -/// and internalgeomprop attributes by using its name. +/// +/// A GeomPropDef element contains a reference to a geometric node and a set of +/// modifiers for that node. For example, a world-space normal can be declared +/// as a reference to the "normal" geometric node with a space setting of +/// "world", or a specific set of texture coordinates can be declared as a +/// reference to the "texcoord" geometric node with an index setting of "1". +/// +/// Once a GeomPropDef has been declared it may be referenced by Input elements +/// through their defaultgeomprop attribute. class GeomPropDef : public Element { public: @@ -387,7 +389,6 @@ class GeomPropDef : public Element return getAttribute(NODE_ATTRIBUTE); } - /// @} /// @name Geometric Space /// @{ @@ -464,7 +465,6 @@ class GeomPropDef : public Element static const string ATTR_NAME_ATTRIBUTE; }; - /// @class Collection /// A collection element within a Document. class Collection : public Element diff --git a/source/MaterialXCore/Interface.cpp b/source/MaterialXCore/Interface.cpp index d7e9d41c5d..2a1ca7b240 100644 --- a/source/MaterialXCore/Interface.cpp +++ b/source/MaterialXCore/Interface.cpp @@ -16,7 +16,7 @@ namespace MaterialX const string PortElement::NODE_NAME_ATTRIBUTE = "nodename"; const string PortElement::OUTPUT_ATTRIBUTE = "output"; const string InterfaceElement::NODE_DEF_ATTRIBUTE = "nodedef"; -const string Input::DEFAULTGEOMPROP = "defaultgeomprop"; +const string Input::DEFAULT_GEOM_PROP_ATTRIBUTE = "defaultgeomprop"; // // PortElement methods @@ -155,15 +155,25 @@ Edge Input::getUpstreamEdge(ConstMaterialPtr material, size_t index) const GeomPropDefPtr Input::getDefaultGeomProp() const { - const string& defaultgeomprop = getAttribute(DEFAULTGEOMPROP); - if (!defaultgeomprop.empty()) + const string& defaultGeomProp = getAttribute(DEFAULT_GEOM_PROP_ATTRIBUTE); + if (!defaultGeomProp.empty()) { ConstDocumentPtr doc = getDocument(); - return doc->getChildOfType(defaultgeomprop); + return doc->getChildOfType(defaultGeomProp); } return nullptr; } +bool Input::validate(string* message) const +{ + bool res = true; + if (hasDefaultGeomPropString()) + { + validateRequire(getDefaultGeomProp() != nullptr, res, message, "Invalid defaultgeomprop string"); + } + return PortElement::validate(message) && res; +} + // // Output methods // diff --git a/source/MaterialXCore/Interface.h b/source/MaterialXCore/Interface.h index 94ae219627..ca2635a00f 100644 --- a/source/MaterialXCore/Interface.h +++ b/source/MaterialXCore/Interface.h @@ -209,29 +209,37 @@ class Input : public PortElement /// Set the defaultgeomprop string for the input. void setDefaultGeomPropString(const string& geomprop) { - setAttribute(DEFAULTGEOMPROP, geomprop); + setAttribute(DEFAULT_GEOM_PROP_ATTRIBUTE, geomprop); } /// Return true if the given input has a defaultgeomprop string. bool hasDefaultGeomPropString() const { - return hasAttribute(DEFAULTGEOMPROP); + return hasAttribute(DEFAULT_GEOM_PROP_ATTRIBUTE); } /// Return the defaultgeomprop string for the input. const string& getDefaultGeomPropString() const { - return getAttribute(DEFAULTGEOMPROP); + return getAttribute(DEFAULT_GEOM_PROP_ATTRIBUTE); } /// Return the GeomPropDef element to use, if defined for this input. GeomPropDefPtr getDefaultGeomProp() const; /// @} + /// @name Validation + /// @{ + + /// Validate that the given element tree, including all descendants, is + /// consistent with the MaterialX specification. + bool validate(string* message = nullptr) const override; + + /// @} public: static const string CATEGORY; - static const string DEFAULTGEOMPROP; + static const string DEFAULT_GEOM_PROP_ATTRIBUTE; }; /// @class Output diff --git a/source/MaterialXCore/Look.cpp b/source/MaterialXCore/Look.cpp index 3389831b68..3612a6662f 100644 --- a/source/MaterialXCore/Look.cpp +++ b/source/MaterialXCore/Look.cpp @@ -16,6 +16,21 @@ const string Visibility::VIEWER_COLLECTION_ATTRIBUTE = "viewercollection"; const string Visibility::VISIBILITY_TYPE_ATTRIBUTE = "vistype"; const string Visibility::VISIBLE_ATTRIBUTE = "visible"; +// +// MaterialAssign methods +// + +vector MaterialAssign::getActiveVariantAssigns() const +{ + vector activeAssigns; + for (ConstElementPtr elem : traverseInheritance()) + { + vector assigns = elem->asA()->getVariantAssigns(); + activeAssigns.insert(activeAssigns.end(), assigns.begin(), assigns.end()); + } + return activeAssigns; +} + // // Look methods // diff --git a/source/MaterialXCore/Look.h b/source/MaterialXCore/Look.h index 47f3236ba8..9d007bdbe2 100644 --- a/source/MaterialXCore/Look.h +++ b/source/MaterialXCore/Look.h @@ -291,7 +291,40 @@ class MaterialAssign : public GeomElement MaterialPtr getReferencedMaterial() const; /// @} + /// @name VariantAssign Elements + /// @{ + /// Add a VariantAssign to the look. + /// @param name The name of the new VariantAssign. + /// If no name is specified, then a unique name will automatically be + /// generated. + /// @return A shared pointer to the new VariantAssign. + VariantAssignPtr addVariantAssign(const string& name = EMPTY_STRING) + { + return addChild(name); + } + + /// Return the VariantAssign, if any, with the given name. + VariantAssignPtr getVariantAssign(const string& name) const + { + return getChildOfType(name); + } + + /// Return a vector of all VariantAssign elements in the look. + vector getVariantAssigns() const + { + return getChildrenOfType(); + } + + /// Return a vector of all VariantAssign elements that belong to this look, + /// taking look inheritance into account. + vector getActiveVariantAssigns() const; + + /// Remove the VariantAssign, if any, with the given name. + void removeVariantAssign(const string& name) + { + removeChildOfType(name); + } public: static const string CATEGORY; static const string MATERIAL_ATTRIBUTE; diff --git a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp index 5712fc0ac6..c92b740c5d 100644 --- a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp +++ b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp @@ -258,6 +258,11 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele HwShader& shader = *shaderPtr; + // 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. + Value::ScopedFloatFormatting fmt(Value::FloatFormatFixed); + // // Emit code for vertex shader stage // @@ -278,7 +283,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele if (!vsConstants.empty()) { shader.addComment("Constant block: " + vsConstants.name); - emitVariableBlock(vsConstants, _syntax->getConstantQualifier(), shader); + emitVariableBlock(vsConstants, _syntax->getConstantQualifier(), SEMICOLON_NEWLINE, shader); } // Add all private uniforms @@ -286,7 +291,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele if (!vsPrivateUniforms.empty()) { shader.addComment("Uniform block: " + vsPrivateUniforms.name); - emitVariableBlock(vsPrivateUniforms, _syntax->getUniformQualifier(), shader); + emitVariableBlock(vsPrivateUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } // Add any public uniforms @@ -294,7 +299,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele if (!vsPublicUniforms.empty()) { shader.addComment("Uniform block: " + vsPublicUniforms.name); - emitVariableBlock(vsPublicUniforms, _syntax->getUniformQualifier(), shader); + emitVariableBlock(vsPublicUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } // Add all app data inputs @@ -322,7 +327,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele shader.addLine(type + " " + output->name); } shader.endScope(false, false); - shader.addStr(" " + vertexDataBlock.instance + ";\n"); + shader.addStr(" " + vertexDataBlock.instance + SEMICOLON_NEWLINE); shader.newLine(); } @@ -358,7 +363,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele if (!psConstants.empty()) { shader.addComment("Constant block: " + psConstants.name); - emitVariableBlock(psConstants, _syntax->getConstantQualifier(), shader); + emitVariableBlock(psConstants, _syntax->getConstantQualifier(), SEMICOLON_NEWLINE, shader); } // Add all private uniforms @@ -366,7 +371,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele if (!psPrivateUniforms.empty()) { shader.addComment("Uniform block: " + psPrivateUniforms.name); - emitVariableBlock(psPrivateUniforms, _syntax->getUniformQualifier(), shader); + emitVariableBlock(psPrivateUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } // Add all public uniforms @@ -374,7 +379,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele if (!psPublicUniforms.empty()) { shader.addComment("Uniform block: " + psPublicUniforms.name); - emitVariableBlock(psPublicUniforms, _syntax->getUniformQualifier(), shader); + emitVariableBlock(psPublicUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } bool lighting = shader.hasClassification(ShaderNode::Classification::SHADER | ShaderNode::Classification::SURFACE) || @@ -408,7 +413,7 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele shader.addLine(type + " " + input->name); } shader.endScope(false, false); - shader.addStr(" " + vertexDataBlock.instance + ";\n"); + shader.addStr(" " + vertexDataBlock.instance + SEMICOLON_NEWLINE); shader.newLine(); } @@ -426,7 +431,14 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele // Emit lighting functions if (lighting) { - shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_lighting.glsl", *this); + if (options.hwSpecularEnvironmentMethod == SPECULAR_ENVIRONMENT_FIS) + { + shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_environment_fis.glsl", *this); + } + else + { + shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_environment_prefilter.glsl", *this); + } shader.newLine(); } @@ -438,6 +450,18 @@ ShaderPtr GlslShaderGenerator::generate(const string& shaderName, ElementPtr ele shader.newLine(); } + // Emit uv transform function + if (options.fileTextureVerticalFlip) + { + shader.addInclude("stdlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_get_target_uv_vflip.glsl", *this); + shader.newLine(); + } + else + { + shader.addInclude("stdlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_get_target_uv_noop.glsl", *this); + shader.newLine(); + } + // Add all functions for node implementations emitFunctionDefinitions(shader); @@ -578,7 +602,7 @@ void GlslShaderGenerator::emitFinalOutput(Shader& shader) const } } -void GlslShaderGenerator::addNodeContextIDs(ShaderNode* node) const +void GlslShaderGenerator::addContextIDs(ShaderNode* node) const { if (node->hasClassification(ShaderNode::Classification::BSDF)) { @@ -607,7 +631,7 @@ void GlslShaderGenerator::addNodeContextIDs(ShaderNode* node) const } else { - ParentClass::addNodeContextIDs(node); + ParentClass::addContextIDs(node); } } @@ -668,7 +692,7 @@ void GlslShaderGenerator::emitBsdfNodes(const ShaderNode& shaderNode, int bsdfCo } else { - // Node is not defined in this context so just + // Node is not defined in this context so just // emit the output variable set to default value. shader.beginLine(); emitOutput(context, node->getOutput(), true, true, shader); @@ -740,7 +764,7 @@ void GlslShaderGenerator::emitVariable(const Shader::Variable& variable, const s if (variable.type == Type::FILENAME) { // Samplers must always be uniforms - shader.addLine("uniform sampler2D " + variable.name); + shader.addStr("uniform sampler2D " + variable.name); } else { @@ -762,7 +786,7 @@ void GlslShaderGenerator::emitVariable(const Shader::Variable& variable, const s { line += " = " + _syntax->getDefaultValue(variable.type, true); } - shader.addLine(line); + shader.addStr(line); } } diff --git a/source/MaterialXGenGlsl/GlslShaderGenerator.h b/source/MaterialXGenGlsl/GlslShaderGenerator.h index d1eadd5beb..8f7dee4a2a 100644 --- a/source/MaterialXGenGlsl/GlslShaderGenerator.h +++ b/source/MaterialXGenGlsl/GlslShaderGenerator.h @@ -14,40 +14,45 @@ a listing of the variables with a description of what data they should be bound ------------------------------------------------------------------------------------------------------------ Vertex input variables : - i_position vec3 Vertex position in object space - i_normal vec3 Vertex normal in object space - i_tangent vec3 Vertex tangent in object space - i_bitangent vec3 Vertex bitangent in object space - i_texcoord_N vec2 Vertex texture coordinate for the N:th uv set - i_color_N vec4 Vertex color for the N:th color set (RGBA) + i_position vec3 Vertex position in object space + i_normal vec3 Vertex normal in object space + i_tangent vec3 Vertex tangent in object space + i_bitangent vec3 Vertex bitangent in object space + i_texcoord_N vec2 Vertex texture coordinate for the N:th uv set + i_color_N vec4 Vertex color for the N:th color set (RGBA) Uniform variables : - u_worldMatrix mat4 World transformation - u_worldInverseMatrix mat4 World transformation, inverted - u_worldTransposeMatrix mat4 World transformation, transposed - u_worldInverseTransposeMatrix mat4 World transformation, inverted and transposed - u_viewMatrix mat4 View transformation - u_viewInverseMatrix mat4 View transformation, inverted - u_viewTransposeMatrix mat4 View transformation, transposed - u_viewInverseTransposeMatrix mat4 View transformation, inverted and transposed - u_projectionMatrix mat4 Projection transformation - u_projectionInverseMatrix mat4 Projection transformation, inverted - u_projectionTransposeMatrix mat4 Projection transformation, transposed - u_projectionInverseTransposeMatrix mat4 Projection transformation, inverted and transposed - u_worldViewMatrix mat4 World-view transformation - u_viewProjectionMatrix mat4 View-projection transformation - u_worldViewProjectionMatrix mat4 World-view-projection transformation - u_viewPosition vec3 World-space position of the view (camera) - u_viewDirection vec3 World-space direction of the view (camera) - u_frame float The current frame number as defined by the host application - u_time float The current time in seconds - u_geomattr_ A named attribute of given where is the name of the variable on the geometry - u_numActiveLightSources int The number of currently active light sources. Note that in shader this is clamped against - the maximum allowed number of lights sources. The maximum number is set by calling - HwShaderGenerator::setMaxActiveLightSources(). - u_lightData[] struct Array of struct LightData holding parameters for active light sources. - The LightData struct is built dynamically depending on requirements for - bound light shaders. + u_worldMatrix mat4 World transformation + u_worldInverseMatrix mat4 World transformation, inverted + u_worldTransposeMatrix mat4 World transformation, transposed + u_worldInverseTransposeMatrix mat4 World transformation, inverted and transposed + u_viewMatrix mat4 View transformation + u_viewInverseMatrix mat4 View transformation, inverted + u_viewTransposeMatrix mat4 View transformation, transposed + u_viewInverseTransposeMatrix mat4 View transformation, inverted and transposed + u_projectionMatrix mat4 Projection transformation + u_projectionInverseMatrix mat4 Projection transformation, inverted + u_projectionTransposeMatrix mat4 Projection transformation, transposed + u_projectionInverseTransposeMatrix mat4 Projection transformation, inverted and transposed + u_worldViewMatrix mat4 World-view transformation + u_viewProjectionMatrix mat4 View-projection transformation + u_worldViewProjectionMatrix mat4 World-view-projection transformation + u_viewPosition vec3 World-space position of the view (camera) + u_viewDirection vec3 World-space direction of the view (camera) + u_frame float The current frame number as defined by the host application + u_time float The current time in seconds + u_geomattr_ A named attribute of given where is the name of the variable on the geometry + u_numActiveLightSources int The number of currently active light sources. Note that in shader this is clamped against + the maximum allowed number of lights sources. The maximum number is set by calling + HwShaderGenerator::setMaxActiveLightSources(). + u_lightData[] struct Array of struct LightData holding parameters for active light sources. + The LightData struct is built dynamically depending on requirements for + bound light shaders. + u_envIrradiance sampler2D Sampler for the texture used for diffuse environment lighting. + u_envRadiance sampler2D Sampler for the texture used for specular environment lighting. + u_envRadianceMips int Number of mipmaps used on the specular environment texture. + u_envMatrix mat4 Rotation matrix for the environment. + u_envSamples int Samples to use if Filtered Importance Sampling is used for specular environment lighting. ------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------ @@ -91,9 +96,9 @@ class GlslShaderGenerator : public HwShaderGenerator /// Emit the final output expression void emitFinalOutput(Shader& shader) const override; - /// Add node contexts id's to the given node to control + /// Add contexts id's to the given node to control /// in which contexts this node should be used - void addNodeContextIDs(ShaderNode* node) const override; + void addContextIDs(ShaderNode* node) const override; /// Given a element attempt to remap a value to an enumeration which is accepted by /// the shader generator. diff --git a/source/MaterialXGenGlsl/GlslSyntax.cpp b/source/MaterialXGenGlsl/GlslSyntax.cpp index eb109256bd..6844b7e86c 100644 --- a/source/MaterialXGenGlsl/GlslSyntax.cpp +++ b/source/MaterialXGenGlsl/GlslSyntax.cpp @@ -64,9 +64,9 @@ namespace class GlslFloatArrayTypeSyntax : public GlslArrayTypeSyntax { public: - GlslFloatArrayTypeSyntax(const string& name) + explicit GlslFloatArrayTypeSyntax(const string& name) : GlslArrayTypeSyntax(name) - {} + {} protected: size_t getSize(const Value& value) const override @@ -80,9 +80,9 @@ namespace class GlslIntegerArrayTypeSyntax : public GlslArrayTypeSyntax { public: - GlslIntegerArrayTypeSyntax(const string& name) + explicit GlslIntegerArrayTypeSyntax(const string& name) : GlslArrayTypeSyntax(name) - {} + {} protected: size_t getSize(const Value& value) const override @@ -108,7 +108,7 @@ GlslSyntax::GlslSyntax() { "centroid", "flat", "smooth", "noperspective", "patch", "sample", "break", "continue", "do", "for", "while", "switch", "case", "default", - "if", "else,", "subroutine", "in", "out", "inout", + "if", "else,", "subroutine", "in", "out", "inout", "float", "double", "int", "void", "bool", "true", "false", "invariant", "discard", "return", "mat2", "mat3", "mat4", "dmat2", "dmat3", "dmat4", @@ -155,7 +155,7 @@ GlslSyntax::GlslSyntax() tokens["webgl_"] = "webgll"; tokens["_webgl"] = "wwebgl"; registerInvalidTokens(tokens); - + // // Register syntax handlers for each data type. // @@ -164,8 +164,8 @@ GlslSyntax::GlslSyntax() ( Type::FLOAT, std::make_shared( - "float", - "0.0", + "float", + "0.0", "0.0") ); @@ -180,8 +180,8 @@ GlslSyntax::GlslSyntax() ( Type::INTEGER, std::make_shared( - "int", - "0", + "int", + "0", "0") ); @@ -196,8 +196,8 @@ GlslSyntax::GlslSyntax() ( Type::BOOLEAN, std::make_shared( - "bool", - "false", + "bool", + "false", "false") ); @@ -205,9 +205,9 @@ GlslSyntax::GlslSyntax() ( Type::COLOR2, std::make_shared( - "vec2", - "vec2(0.0)", - "vec2(0.0)", + "vec2", + "vec2(0.0)", + "vec2(0.0)", EMPTY_STRING, EMPTY_STRING, VEC2_MEMBERS) @@ -217,9 +217,9 @@ GlslSyntax::GlslSyntax() ( Type::COLOR3, std::make_shared( - "vec3", - "vec3(0.0)", - "vec3(0.0)", + "vec3", + "vec3(0.0)", + "vec3(0.0)", EMPTY_STRING, EMPTY_STRING, VEC3_MEMBERS) @@ -229,9 +229,9 @@ GlslSyntax::GlslSyntax() ( Type::COLOR4, std::make_shared( - "vec4", - "vec4(0.0)", - "vec4(0.0)", + "vec4", + "vec4(0.0)", + "vec4(0.0)", EMPTY_STRING, EMPTY_STRING, VEC4_MEMBERS) @@ -241,11 +241,11 @@ GlslSyntax::GlslSyntax() ( Type::VECTOR2, std::make_shared( - "vec2", - "vec2(0.0)", - "vec2(0.0)", - EMPTY_STRING, - EMPTY_STRING, + "vec2", + "vec2(0.0)", + "vec2(0.0)", + EMPTY_STRING, + EMPTY_STRING, VEC2_MEMBERS) ); @@ -253,9 +253,9 @@ GlslSyntax::GlslSyntax() ( Type::VECTOR3, std::make_shared( - "vec3", - "vec3(0.0)", - "vec3(0.0)", + "vec3", + "vec3(0.0)", + "vec3(0.0)", EMPTY_STRING, EMPTY_STRING, VEC3_MEMBERS) @@ -265,9 +265,9 @@ GlslSyntax::GlslSyntax() ( Type::VECTOR4, std::make_shared( - "vec4", - "vec4(0.0)", - "vec4(0.0)", + "vec4", + "vec4(0.0)", + "vec4(0.0)", EMPTY_STRING, EMPTY_STRING, VEC4_MEMBERS) @@ -277,8 +277,8 @@ GlslSyntax::GlslSyntax() ( Type::MATRIX33, std::make_shared( - "mat3", - "mat3(1.0)", + "mat3", + "mat3(1.0)", "mat3(1.0)") ); @@ -286,8 +286,8 @@ GlslSyntax::GlslSyntax() ( Type::MATRIX44, std::make_shared( - "mat4", - "mat4(1.0)", + "mat4", + "mat4(1.0)", "mat4(1.0)") ); @@ -301,8 +301,8 @@ GlslSyntax::GlslSyntax() ( Type::FILENAME, std::make_shared( - "sampler2D", - EMPTY_STRING, + "sampler2D", + EMPTY_STRING, EMPTY_STRING) ); @@ -310,9 +310,9 @@ GlslSyntax::GlslSyntax() ( Type::BSDF, std::make_shared( - "BSDF", - "BSDF(0.0)", - "BSDF(0.0)", + "BSDF", + "BSDF(0.0)", + "BSDF(0.0)", "vec3") ); @@ -320,9 +320,9 @@ GlslSyntax::GlslSyntax() ( Type::EDF, std::make_shared( - "EDF", - "EDF(0.0)", - "EDF(0.0)", + "EDF", + "EDF(0.0)", + "EDF(0.0)", "vec3") ); @@ -330,9 +330,9 @@ GlslSyntax::GlslSyntax() ( Type::VDF, std::make_shared( - "VDF", - "VDF(vec3(0.0),vec3(0.0))", - EMPTY_STRING, + "VDF", + "VDF(vec3(0.0),vec3(0.0))", + EMPTY_STRING, EMPTY_STRING, "struct VDF { vec3 absorption; vec3 scattering; };") ); @@ -352,8 +352,8 @@ GlslSyntax::GlslSyntax() ( Type::SURFACESHADER, std::make_shared( - "surfaceshader", - "surfaceshader(vec3(0.0),vec3(0.0))", + "surfaceshader", + "surfaceshader(vec3(0.0),vec3(0.0))", EMPTY_STRING, EMPTY_STRING, "struct surfaceshader { vec3 color; vec3 transparency; };") @@ -385,8 +385,8 @@ GlslSyntax::GlslSyntax() ( Type::LIGHTSHADER, std::make_shared( - "lightshader", - "lightshader(vec3(0.0),vec3(0.0))", + "lightshader", + "lightshader(vec3(0.0),vec3(0.0))", EMPTY_STRING, EMPTY_STRING, "struct lightshader { vec3 intensity; vec3 direction; };") @@ -399,8 +399,8 @@ const string& GlslSyntax::getOutputQualifier() const } bool GlslSyntax::typeSupported(const TypeDesc* type) const -{ - return type != Type::STRING; +{ + return type != Type::STRING; } } diff --git a/source/MaterialXGenGlsl/Nodes/LightCompoundNodeGlsl.cpp b/source/MaterialXGenGlsl/Nodes/LightCompoundNodeGlsl.cpp index b69a87734a..c0f6be4aa7 100644 --- a/source/MaterialXGenGlsl/Nodes/LightCompoundNodeGlsl.cpp +++ b/source/MaterialXGenGlsl/Nodes/LightCompoundNodeGlsl.cpp @@ -94,10 +94,10 @@ void LightCompoundNodeGlsl::emitFunctionDefinition(const ShaderNode& node, Shade // Emit function definitions for each context used by this compound node for (int id : node.getContextIDs()) { - const GenContext* context = shadergen.getNodeContext(id); + const GenContext* context = shadergen.getContext(id); if (!context) { - throw ExceptionShaderGenError("Node '" + node.getName() + "' has an implementation context that is undefined for shader generator '" + + throw ExceptionShaderGenError("Node '" + node.getName() + "' has a context id that is undefined for shader generator '" + shadergen.getLanguage() + "/" + shadergen.getTarget() + "'"); } diff --git a/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.cpp b/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.cpp index 5b4c927f84..6132c5790f 100644 --- a/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.cpp +++ b/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.cpp @@ -15,7 +15,7 @@ MayaGlslPluginShader::MayaGlslPluginShader(const string& name) { } -void MayaGlslPluginShader::createUniform(size_t stage, const string& block, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) +void MayaGlslPluginShader::createUniform(const string& stage, const string& block, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) { // If no semantic is given and this is the view position uniform // we need to override its default semantic diff --git a/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.h b/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.h index 64bbb689c3..59240ead73 100644 --- a/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.h +++ b/source/MaterialXGenOgsFx/MayaGlslPluginShaderGenerator.h @@ -17,7 +17,7 @@ class MayaGlslPluginShader : public OgsFxShader public: MayaGlslPluginShader(const string& name); - void createUniform(size_t stage, const string& block, const TypeDesc* type, const string& name, const string& path = EMPTY_STRING, const string& semantic = EMPTY_STRING, ValuePtr value = nullptr) override; + void createUniform(const string& stage, const string& block, const TypeDesc* type, const string& name, const string& path = EMPTY_STRING, const string& semantic = EMPTY_STRING, ValuePtr value = nullptr) override; void createAppData(const TypeDesc* type, const string& name, const string& semantic = EMPTY_STRING) override; void createVertexData(const TypeDesc* type, const string& name, const string& semantic = EMPTY_STRING) override; }; diff --git a/source/MaterialXGenOgsFx/OgsFxShaderGenerator.cpp b/source/MaterialXGenOgsFx/OgsFxShaderGenerator.cpp index 776b9e3112..250d5971fb 100644 --- a/source/MaterialXGenOgsFx/OgsFxShaderGenerator.cpp +++ b/source/MaterialXGenOgsFx/OgsFxShaderGenerator.cpp @@ -64,19 +64,22 @@ namespace }; } -OgsFxShader::OgsFxShader(const string& name) + +const string OgsFxShader::FINAL_FX_STAGE = "finalfx"; + +OgsFxShader::OgsFxShader(const string& name) : ParentClass(name) { - _stages.push_back(Stage("FinalFx")); + createStage(FINAL_FX_STAGE); // Create default uniform blocks for final fx stage createUniformBlock(FINAL_FX_STAGE, PRIVATE_UNIFORMS, "prvUniform"); createUniformBlock(FINAL_FX_STAGE, PUBLIC_UNIFORMS, "pubUniform"); } -void OgsFxShader::createUniform(size_t stage, const string& block, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) +void OgsFxShader::createUniform(const string& stage, const string& block, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) { - // If no semantic is given check if we have + // If no semantic is given check if we have // an OgsFx semantic that should be used if (semantic.empty()) { @@ -92,7 +95,7 @@ void OgsFxShader::createUniform(size_t stage, const string& block, const TypeDes void OgsFxShader::createAppData(const TypeDesc* type, const string& name, const string& semantic) { - // If no semantic is given check if we have + // If no semantic is given check if we have // an OgsFx semantic that should be used if (semantic.empty()) { @@ -108,7 +111,7 @@ void OgsFxShader::createAppData(const TypeDesc* type, const string& name, const void OgsFxShader::createVertexData(const TypeDesc* type, const string& name, const string& semantic) { - // If no semantic is given check if we have + // If no semantic is given check if we have // an OgsFx semantic that should be used if (semantic.empty()) { @@ -166,7 +169,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el if (!vsConstants.empty()) { shader.addComment("Constant block: " + vsConstants.name); - emitVariableBlock(vsConstants, _syntax->getConstantQualifier(), shader); + emitVariableBlock(vsConstants, _syntax->getConstantQualifier(), SEMICOLON_NEWLINE, shader); } // Add main function @@ -184,7 +187,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el // shader.setActiveStage(OgsFxShader::PIXEL_STAGE); - + shader.addComment("---------------------------------- Pixel shader ----------------------------------------\n"); shader.addStr("GLSLShader PS\n"); shader.beginScope(Shader::Brackets::BRACES); @@ -208,6 +211,18 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el shader.newLine(); } + // Emit uv transform function + if (options.fileTextureVerticalFlip) + { + shader.addInclude("stdlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_get_target_uv_vflip.glsl", *this); + shader.newLine(); + } + else + { + shader.addInclude("stdlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_get_target_uv_noop.glsl", *this); + shader.newLine(); + } + emitFunctionDefinitions(shader); // Add constants @@ -215,7 +230,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el if (!psConstants.empty()) { shader.addComment("Constant block: " + psConstants.name); - emitVariableBlock(psConstants, _syntax->getConstantQualifier(), shader); + emitVariableBlock(psConstants, _syntax->getConstantQualifier(), SEMICOLON_NEWLINE, shader); } // Add main function @@ -254,7 +269,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el // Assemble the final effects shader // - shader.setActiveStage(size_t(OgsFxShader::FINAL_FX_STAGE)); + shader.setActiveStage(OgsFxShader::FINAL_FX_STAGE); // Add version directive shader.addLine("#version " + getVersion(), false); @@ -305,11 +320,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el if (!vsPrivateUniforms.empty()) { shader.addComment("Vertex stage uniform block: " + vsPrivateUniforms.name); - for (const Shader::Variable* uniform : vsPrivateUniforms.variableOrder) - { - emitUniform(*uniform, shader); - } - shader.newLine(); + emitVariableBlock(vsPrivateUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } // Add all public vertex shader uniforms @@ -317,11 +328,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el if (!vsPublicUniforms.empty()) { shader.addComment("Vertex stage uniform block: " + vsPublicUniforms.name); - for (const Shader::Variable* uniform : vsPublicUniforms.variableOrder) - { - emitUniform(*uniform, shader); - } - shader.newLine(); + emitVariableBlock(vsPublicUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } // Add all private pixel shader uniforms @@ -329,11 +336,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el if (!psPrivateUniforms.empty()) { shader.addComment("Pixel stage uniform block: " + psPrivateUniforms.name); - for (const Shader::Variable* uniform : psPrivateUniforms.variableOrder) - { - emitUniform(*uniform, shader); - } - shader.newLine(); + emitVariableBlock(psPrivateUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } // Add all public pixel shader uniforms @@ -341,11 +344,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el if (!psPublicUniforms.empty()) { shader.addComment("Pixel stage uniform block: " + psPublicUniforms.name); - for (const Shader::Variable* uniform : psPublicUniforms.variableOrder) - { - emitUniform(*uniform, shader); - } - shader.newLine(); + emitVariableBlock(psPublicUniforms, _syntax->getUniformQualifier(), SEMICOLON_NEWLINE, shader); } if (lighting) @@ -374,7 +373,17 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el shader.beginScope(Shader::Brackets::BRACES); shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/" + OgsFxShaderGenerator::TARGET + "/mx_lighting_functions.glsl", *this); shader.newLine(); - shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_lighting.glsl", *this); + + // Emit environment lighting functions + if (options.hwSpecularEnvironmentMethod == SPECULAR_ENVIRONMENT_FIS) + { + shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_environment_fis.glsl", *this); + } + else + { + shader.addInclude("pbrlib/" + GlslShaderGenerator::LANGUAGE + "/lib/mx_environment_prefilter.glsl", *this); + } + shader.endScope(); shader.newLine(); } @@ -398,7 +407,7 @@ ShaderPtr OgsFxShaderGenerator::generate(const string& shaderName, ElementPtr el shader.addLine("pass p0", false); shader.beginScope(Shader::Brackets::BRACES); shader.addLine("VertexShader(in AppData, out VertexData vd) = { VS }"); - shader.addLine(lighting ? + shader.addLine(lighting ? "PixelShader(in VertexData vd, out PixelOutput) = { LightingFunctions, PS }" : "PixelShader(in VertexData vd, out PixelOutput) = { PS }"); shader.endScope(); diff --git a/source/MaterialXGenOgsFx/OgsFxShaderGenerator.h b/source/MaterialXGenOgsFx/OgsFxShaderGenerator.h index 2bb231fc25..7c8fcc078c 100644 --- a/source/MaterialXGenOgsFx/OgsFxShaderGenerator.h +++ b/source/MaterialXGenOgsFx/OgsFxShaderGenerator.h @@ -18,15 +18,12 @@ class OgsFxShader : public HwShader public: /// Identifier for final effects stage - static const size_t FINAL_FX_STAGE = HwShader::NUM_STAGES; - static const size_t NUM_STAGES = HwShader::NUM_STAGES + 1; + static const string FINAL_FX_STAGE; public: OgsFxShader(const string& name); - size_t numStages() const override { return NUM_STAGES; } - - void createUniform(size_t stage, const string& block, const TypeDesc* type, const string& name, const string& path = EMPTY_STRING, const string& semantic = EMPTY_STRING, ValuePtr value = nullptr) override; + void createUniform(const string& stage, const string& block, const TypeDesc* type, const string& name, const string& path = EMPTY_STRING, const string& semantic = EMPTY_STRING, ValuePtr value = nullptr) override; void createAppData(const TypeDesc* type, const string& name, const string& semantic = EMPTY_STRING) override; void createVertexData(const TypeDesc* type, const string& name, const string& semantic = EMPTY_STRING) override; }; diff --git a/source/MaterialXGenOsl/OslShaderGenerator.cpp b/source/MaterialXGenOsl/OslShaderGenerator.cpp index b6bd6011c4..348b286ff5 100644 --- a/source/MaterialXGenOsl/OslShaderGenerator.cpp +++ b/source/MaterialXGenOsl/OslShaderGenerator.cpp @@ -169,6 +169,18 @@ ShaderPtr OslShaderGenerator::generate(const string& shaderName, ElementPtr elem shader.newLine(); } + // Emit uv transform function + if (options.fileTextureVerticalFlip) + { + shader.addInclude("stdlib/" + OslShaderGenerator::LANGUAGE + "/lib/mx_get_target_uv_vflip.osl", *this); + shader.newLine(); + } + else + { + shader.addInclude("stdlib/" + OslShaderGenerator::LANGUAGE + "/lib/mx_get_target_uv_noop.osl", *this); + shader.newLine(); + } + emitFunctionDefinitions(shader); // Emit shader type @@ -204,13 +216,7 @@ ShaderPtr OslShaderGenerator::generate(const string& shaderName, ElementPtr elem // Emit all public inputs const Shader::VariableBlock& publicUniforms = shader.getUniformBlock(Shader::PIXEL_STAGE, Shader::PUBLIC_UNIFORMS); - for (const Shader::Variable* uniform : publicUniforms.variableOrder) - { - shader.beginLine(); - emitUniform(*uniform, shader); - shader.addStr(","); - shader.endLine(false); - } + emitVariableBlock(publicUniforms, _syntax->getUniformQualifier(), COMMA, shader); // Emit shader output const TypeDesc* outputType = outputSocket->type; @@ -229,7 +235,7 @@ ShaderPtr OslShaderGenerator::generate(const string& shaderName, ElementPtr elem for (const Shader::Variable* constant : psConstants.variableOrder) { shader.beginLine(); - emitConstant(*constant, shader); + emitVariable(*constant, _syntax->getConstantQualifier(), shader); shader.endLine(); } shader.newLine(); diff --git a/source/MaterialXGenOsl/OslShaderGenerator.h b/source/MaterialXGenOsl/OslShaderGenerator.h index 6899bb4024..53e4fc6957 100644 --- a/source/MaterialXGenOsl/OslShaderGenerator.h +++ b/source/MaterialXGenOsl/OslShaderGenerator.h @@ -6,6 +6,8 @@ namespace MaterialX { +using OslShaderGeneratorPtr = shared_ptr; + /// Base class for OSL (Open Shading Language) shader generators. /// A generator for a specific OSL target should be derived from this class. class OslShaderGenerator : public ShaderGenerator diff --git a/source/MaterialXGenOsl/OslSyntax.cpp b/source/MaterialXGenOsl/OslSyntax.cpp index dece290a86..3be12b4f49 100644 --- a/source/MaterialXGenOsl/OslSyntax.cpp +++ b/source/MaterialXGenOsl/OslSyntax.cpp @@ -46,7 +46,7 @@ namespace return result; } - + protected: virtual bool isEmpty(const Value& value) const = 0; }; @@ -54,7 +54,7 @@ namespace class OslFloatArrayTypeSyntax : public OslArrayTypeSyntax { public: - OslFloatArrayTypeSyntax(const string& name) + explicit OslFloatArrayTypeSyntax(const string& name) : OslArrayTypeSyntax(name) {} @@ -69,7 +69,7 @@ namespace class OslIntegerArrayTypeSyntax : public OslArrayTypeSyntax { public: - OslIntegerArrayTypeSyntax(const string& name) + explicit OslIntegerArrayTypeSyntax(const string& name) : OslArrayTypeSyntax(name) {} @@ -81,13 +81,13 @@ namespace } }; - // In OSL vector2, vector4, color2 and color4 are custom struct types and require a different + // In OSL vector2, vector4, color2 and color4 are custom struct types and require a different // value syntax for uniforms. So override the aggregate type syntax to support this. class OslStructTypeSyntax : public AggregateTypeSyntax { public: OslStructTypeSyntax(const string& name, const string& defaultValue, const string& uniformDefaultValue, - const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, + const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, const vector& members = EMPTY_MEMBERS) : AggregateTypeSyntax(name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members) {} @@ -132,7 +132,7 @@ namespace class OslColor4TypeSyntax : public OslStructTypeSyntax { public: - OslColor4TypeSyntax() + OslColor4TypeSyntax() : OslStructTypeSyntax("color4", "color4(color(0.0), 0.0)", "{color(0.0), 0.0}", EMPTY_STRING, EMPTY_STRING, OslSyntax::COLOR4_MEMBERS) {} @@ -200,7 +200,7 @@ OslSyntax::OslSyntax() "false", "friend", "goto", "inline", "long", "new", "operator", "private", "protected", "short", "signed", "sizeof", "static", "switch", "template", "this", "throw", "true", "try", "typedef", "uniform", "union", "unsigned", "varying", "virtual", "volatile", - "emission", "background", "diffuse", "oren_nayer", "translucent", "phong", "ward", "microfacet", + "emission", "background", "diffuse", "oren_nayer", "translucent", "phong", "ward", "microfacet", "reflection", "transparent", "debug", "holdout", "subsurface", "" }); @@ -213,8 +213,8 @@ OslSyntax::OslSyntax() ( Type::FLOAT, std::make_shared( - "float", - "0.0", + "float", + "0.0", "0.0") ); @@ -229,7 +229,7 @@ OslSyntax::OslSyntax() ( Type::INTEGER, std::make_shared( - "int", + "int", "0", "0") ); @@ -245,9 +245,9 @@ OslSyntax::OslSyntax() ( Type::BOOLEAN, std::make_shared( - "int", - "0", - "0", + "int", + "0", + "0", EMPTY_STRING, "#define true 1\n#define false 0") ); @@ -256,9 +256,9 @@ OslSyntax::OslSyntax() ( Type::COLOR2, std::make_shared( - "color2", - "color2(0.0, 0.0)", - "{0.0, 0.0}", + "color2", + "color2(0.0, 0.0)", + "{0.0, 0.0}", EMPTY_STRING, EMPTY_STRING, COLOR2_MEMBERS) @@ -266,12 +266,12 @@ OslSyntax::OslSyntax() registerTypeSyntax ( - // Note: the color type in OSL is a built in type and + // Note: the color type in OSL is a built in type and // should not use the custom OslStructTypeSyntax. Type::COLOR3, std::make_shared( - "color", - "color(0.0)", + "color", + "color(0.0)", "color(0.0)", EMPTY_STRING, EMPTY_STRING, @@ -288,8 +288,8 @@ OslSyntax::OslSyntax() ( Type::VECTOR2, std::make_shared( - "vector2", - "vector2(0.0, 0.0)", + "vector2", + "vector2(0.0, 0.0)", "{0.0, 0.0}", EMPTY_STRING, EMPTY_STRING, @@ -298,12 +298,12 @@ OslSyntax::OslSyntax() registerTypeSyntax ( - // Note: the vector type in OSL is a built in type and + // Note: the vector type in OSL is a built in type and // should not use the custom OslStructTypeSyntax. Type::VECTOR3, std::make_shared( - "vector", - "vector(0.0)", + "vector", + "vector(0.0)", "vector(0.0)", EMPTY_STRING, EMPTY_STRING, @@ -314,8 +314,8 @@ OslSyntax::OslSyntax() ( Type::VECTOR4, std::make_shared( - "vector4", - "vector4(0.0, 0.0, 0.0, 0.0)", + "vector4", + "vector4(0.0, 0.0, 0.0, 0.0)", "{0.0, 0.0, 0.0, 0.0}", EMPTY_STRING, EMPTY_STRING, @@ -326,8 +326,8 @@ OslSyntax::OslSyntax() ( Type::MATRIX33, std::make_shared( - "matrix", - "matrix(1.0)", + "matrix", + "matrix(1.0)", "matrix(1.0)") ); @@ -335,8 +335,8 @@ OslSyntax::OslSyntax() ( Type::MATRIX44, std::make_shared( - "matrix", - "matrix(1.0)", + "matrix", + "matrix(1.0)", "matrix(1.0)") ); @@ -344,8 +344,8 @@ OslSyntax::OslSyntax() ( Type::STRING, std::make_shared( - "string", - "\"\"", + "string", + "\"\"", "\"\"") ); @@ -353,8 +353,8 @@ OslSyntax::OslSyntax() ( Type::FILENAME, std::make_shared( - "string", - "\"\"", + "string", + "\"\"", "\"\"") ); @@ -362,9 +362,9 @@ OslSyntax::OslSyntax() ( Type::BSDF, std::make_shared( - "BSDF", - "null_closure", - "0", + "BSDF", + "null_closure", + "0", "closure color") ); @@ -372,9 +372,9 @@ OslSyntax::OslSyntax() ( Type::EDF, std::make_shared( - "EDF", - "null_closure", - "0", + "EDF", + "null_closure", + "0", "closure color") ); @@ -382,9 +382,9 @@ OslSyntax::OslSyntax() ( Type::VDF, std::make_shared( - "VDF", - "null_closure", - "0", + "VDF", + "null_closure", + "0", "closure color") ); @@ -403,9 +403,9 @@ OslSyntax::OslSyntax() ( Type::SURFACESHADER, std::make_shared( - "surfaceshader", - "null_closure", - "0", + "surfaceshader", + "null_closure", + "0", "closure color") ); @@ -413,9 +413,9 @@ OslSyntax::OslSyntax() ( Type::VOLUMESHADER, std::make_shared( - "volumeshader", - "null_closure", - "0", + "volumeshader", + "null_closure", + "0", "closure color") ); @@ -434,9 +434,9 @@ OslSyntax::OslSyntax() ( Type::LIGHTSHADER, std::make_shared( - "lightshader", - "null_closure", - "0", + "lightshader", + "null_closure", + "0", "closure color") ); } diff --git a/source/MaterialXGenShader/DefaultColorManagementSystem.h b/source/MaterialXGenShader/DefaultColorManagementSystem.h index b95568f6e6..c100ffcfc9 100644 --- a/source/MaterialXGenShader/DefaultColorManagementSystem.h +++ b/source/MaterialXGenShader/DefaultColorManagementSystem.h @@ -28,15 +28,16 @@ class DefaultColorManagementSystem : public ColorManagementSystem return DefaultColorManagementSystem::CMS_NAME; } + static const string CMS_NAME; + + protected: /// Returns an implementation name for a given transform string getImplementationName(const ColorSpaceTransform& transform) override; - static const string CMS_NAME; - - private: /// Protected constructor DefaultColorManagementSystem(const string& language); + private: string _language; }; diff --git a/source/MaterialXGenShader/GenOptions.cpp b/source/MaterialXGenShader/GenOptions.cpp index a55f62eaf3..6a9156d4bb 100644 --- a/source/MaterialXGenShader/GenOptions.cpp +++ b/source/MaterialXGenShader/GenOptions.cpp @@ -6,7 +6,8 @@ namespace MaterialX GenOptions::GenOptions() : shaderInterfaceType(SHADER_INTERFACE_COMPLETE) , hwTransparency(false) - , validate(false) + , hwSpecularEnvironmentMethod(SPECULAR_ENVIRONMENT_PREFILTER) + , fileTextureVerticalFlip(false) { } diff --git a/source/MaterialXGenShader/GenOptions.h b/source/MaterialXGenShader/GenOptions.h index 74d725524e..b90713affe 100644 --- a/source/MaterialXGenShader/GenOptions.h +++ b/source/MaterialXGenShader/GenOptions.h @@ -1,7 +1,7 @@ #ifndef MATERIALX_GENOPTIONS_H #define MATERIALX_GENOPTIONS_H -#include +#include namespace MaterialX { @@ -23,6 +23,18 @@ enum ShaderInterfaceType SHADER_INTERFACE_REDUCED }; +/// Method to use for specular environment lighting +enum HwSpecularEnvironmentMethod +{ + /// Use pre-filtered environment maps for + /// specular environment/indirect lighting. + SPECULAR_ENVIRONMENT_PREFILTER, + + /// Use Filtered Importance Sampling for + /// specular environment/indirect lighting. + SPECULAR_ENVIRONMENT_FIS +}; + /// Class holding options to configure shader generation. class GenOptions { @@ -45,11 +57,20 @@ class GenOptions /// the surface will be fully opaque. bool hwTransparency; - /// An optional override for the target color space - std::string targetColorSpaceOverride; + /// Sets the method to use for specular environment + /// lighting for HW shader targets. + int hwSpecularEnvironmentMethod; + + /// If true the y-component of texture coordinates used for sampling + /// file textures will be flipped before sampling. This can be used if + /// file textures need to be flipped vertically to match the target's + /// texture space convention. By default this option is false. + bool fileTextureVerticalFlip; - /// Sets whether to perform a validation check before generation - bool validate; + /// An optional override for the target color space. + /// Shader fragments will be generated to transform + /// input values and textures into this color space. + string targetColorSpaceOverride; }; } // namespace MaterialX diff --git a/source/MaterialXGenShader/HwLightHandler.cpp b/source/MaterialXGenShader/HwLightHandler.cpp deleted file mode 100644 index 4bf5ed80a1..0000000000 --- a/source/MaterialXGenShader/HwLightHandler.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include - -namespace MaterialX -{ - -unsigned int HwLightHandler::getLightType(NodePtr node) -{ - size_t hash = std::hash{}(node->getCategory()); - - // If we're on a 64-bit platform, convert to 32-bits - bool on64BitPlatform = sizeof(hash) == 8; - if (on64BitPlatform) - { - // Convert hash to 32-bits - return static_cast(hash & 0xFFFFFFFF) ^ - static_cast((static_cast(hash) >> 32) & 0xFFFFFFFF); - } - else - { - return static_cast(hash); - } -} - -HwLightHandler::HwLightHandler() -{ -} - -HwLightHandler::~HwLightHandler() -{ -} - -void HwLightHandler::addLightSource(NodePtr node) -{ - _lightSources.push_back(node); -} - -void HwLightHandler::bindLightShaders(HwShaderGenerator& shadergen, const GenOptions& options) const -{ - for (auto lightSource : _lightSources) - { - shadergen.bindLightShader(*lightSource->getNodeDef(), getLightType(lightSource), options); - } -} - -} diff --git a/source/MaterialXGenShader/HwShader.cpp b/source/MaterialXGenShader/HwShader.cpp index 757f4b728b..b683def723 100644 --- a/source/MaterialXGenShader/HwShader.cpp +++ b/source/MaterialXGenShader/HwShader.cpp @@ -4,6 +4,7 @@ namespace MaterialX { +const string HwShader::VERTEX_STAGE = "vertex"; const string HwShader::LIGHT_DATA_BLOCK = "LightData"; HwShader::HwShader(const string& name) @@ -11,7 +12,8 @@ HwShader::HwShader(const string& name) , _vertexData("VertexData", "vd") , _transparency(false) { - _stages.push_back(Stage("Vertex")); + // Create the vertex stage + createStage(VERTEX_STAGE); // Create default uniform blocks for vertex stage createUniformBlock(VERTEX_STAGE, PRIVATE_UNIFORMS, "prvUniform"); @@ -30,9 +32,13 @@ HwShader::HwShader(const string& name) 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1); - createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::MATRIX44, "u_envMatrix", EMPTY_STRING, + createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::MATRIX44, "u_envMatrix", EMPTY_STRING, EMPTY_STRING, Value::createValue(yRotationPI)); - createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::FILENAME, "u_envSpecular"); + createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::INTEGER, "u_envSamples", EMPTY_STRING, + EMPTY_STRING, Value::createValue(16)); + createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::FILENAME, "u_envRadiance"); + createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::INTEGER, "u_envRadianceMips", EMPTY_STRING, + EMPTY_STRING, Value::createValue(1)); createUniform(PIXEL_STAGE, PRIVATE_UNIFORMS, Type::FILENAME, "u_envIrradiance"); } diff --git a/source/MaterialXGenShader/HwShader.h b/source/MaterialXGenShader/HwShader.h index 1e388a9b71..0dfd3162a5 100644 --- a/source/MaterialXGenShader/HwShader.h +++ b/source/MaterialXGenShader/HwShader.h @@ -15,8 +15,7 @@ class HwShader : public Shader public: /// Identifier for additional vertex shader stage - static const size_t VERTEX_STAGE = Shader::NUM_STAGES; - static const size_t NUM_STAGES = Shader::NUM_STAGES + 1; + static const string VERTEX_STAGE; /// Identifier for light data uniform block. static const string LIGHT_DATA_BLOCK; @@ -30,9 +29,6 @@ class HwShader : public Shader /// @param options Generation options void initialize(ElementPtr element, ShaderGenerator& shadergen, const GenOptions& options) override; - /// Return the number of shader stages for this shader. - size_t numStages() const override { return NUM_STAGES; } - /// Create a new variable for vertex data. This creates an /// output from the vertex stage and and input to the pixel stage. virtual void createVertexData(const TypeDesc* type, const string& name, const string& semantic = EMPTY_STRING); diff --git a/source/MaterialXGenShader/HwShaderGenerator.cpp b/source/MaterialXGenShader/HwShaderGenerator.cpp index dd3c6dffd7..cc0d446e9e 100644 --- a/source/MaterialXGenShader/HwShaderGenerator.cpp +++ b/source/MaterialXGenShader/HwShaderGenerator.cpp @@ -12,7 +12,7 @@ HwShaderGenerator::HwShaderGenerator(SyntaxPtr syntax) { } -void HwShaderGenerator::bindLightShader(const NodeDef& nodeDef, size_t lightTypeId, const GenOptions& options) +void HwShaderGenerator::bindLightShader(const NodeDef& nodeDef, unsigned int lightTypeId, const GenOptions& options) { if (TypeDesc::get(nodeDef.getType()) != Type::LIGHTSHADER) { @@ -21,7 +21,8 @@ void HwShaderGenerator::bindLightShader(const NodeDef& nodeDef, size_t lightType if (getBoundLightShader(lightTypeId)) { - throw ExceptionShaderGenError("Error binding light shader. Light type id '" + std::to_string(lightTypeId) + "' has already been bound"); + throw ExceptionShaderGenError("Error binding light shader. Light type id '" + std::to_string(lightTypeId) + + "' has already been bound"); } ShaderNodeImplPtr sgimpl; @@ -52,7 +53,7 @@ void HwShaderGenerator::bindLightShader(const NodeDef& nodeDef, size_t lightType _boundLightShaders[lightTypeId] = sgimpl; } -ShaderNodeImpl* HwShaderGenerator::getBoundLightShader(size_t lightTypeId) +ShaderNodeImpl* HwShaderGenerator::getBoundLightShader(unsigned int lightTypeId) { auto it = _boundLightShaders.find(lightTypeId); return it != _boundLightShaders.end() ? it->second.get() : nullptr; diff --git a/source/MaterialXGenShader/HwShaderGenerator.h b/source/MaterialXGenShader/HwShaderGenerator.h index 5010af9d3a..1f4853d414 100644 --- a/source/MaterialXGenShader/HwShaderGenerator.h +++ b/source/MaterialXGenShader/HwShaderGenerator.h @@ -2,6 +2,7 @@ #define MATERIALX_HWSHADERGENERATOR_H #include +#include namespace MaterialX { @@ -16,16 +17,19 @@ class HwShaderGenerator : public ShaderGenerator public: /// Set the maximum number of light sources that can be active at once. - void setMaxActiveLightSources(size_t count) { _maxActiveLightSources = count; } + void setMaxActiveLightSources(unsigned int count) + { + _maxActiveLightSources = std::max((unsigned int)1, count); + } /// Get the maximum number of light sources that can be active at once. - size_t getMaxActiveLightSources() const { return _maxActiveLightSources; } + unsigned int getMaxActiveLightSources() const { return _maxActiveLightSources; } /// Bind a light shader to a light type id, for usage in surface shaders created /// by the generator. The lightTypeId should be a unique identifier for the light - /// type and the same id should be used when setting light parameters on a + /// type (node definition) and the same id should be used when setting light parameters on a /// generated surface shader. - void bindLightShader(const NodeDef& nodeDef, size_t lightTypeId, const GenOptions& options); + void bindLightShader(const NodeDef& nodeDef, unsigned int lightTypeId, const GenOptions& options); /// Return a map of all light shaders that has been bound. The map contains the /// light shader implementations with their bound light type id's. @@ -34,12 +38,12 @@ class HwShaderGenerator : public ShaderGenerator /// Return the light shader implementation for the given light type id. /// If no light shader with that light type has been bound a nullptr is /// returned instead. - ShaderNodeImpl* getBoundLightShader(size_t lightTypeId); + ShaderNodeImpl* getBoundLightShader(unsigned int lightTypeId); protected: HwShaderGenerator(SyntaxPtr syntax); - size_t _maxActiveLightSources; + unsigned int _maxActiveLightSources; LightShaderMap _boundLightShaders; }; diff --git a/source/MaterialXGenShader/Nodes/CompoundNode.cpp b/source/MaterialXGenShader/Nodes/CompoundNode.cpp index 41873f1f94..159744c865 100644 --- a/source/MaterialXGenShader/Nodes/CompoundNode.cpp +++ b/source/MaterialXGenShader/Nodes/CompoundNode.cpp @@ -62,10 +62,10 @@ void CompoundNode::emitFunctionDefinition(const ShaderNode& node, ShaderGenerato // Emit function definitions for each context used by this compound node for (int id : node.getContextIDs()) { - const GenContext* context = shadergen.getNodeContext(id); + const GenContext* context = shadergen.getContext(id); if (!context) { - throw ExceptionShaderGenError("Node '" + node.getName() + "' has an implementation context that is undefined for shader generator '" + + throw ExceptionShaderGenError("Node '" + node.getName() + "' has a context id that is undefined for shader generator '" + shadergen.getLanguage() + "/" +shadergen.getTarget() + "'"); } diff --git a/source/MaterialXGenShader/Nodes/SourceCodeNode.cpp b/source/MaterialXGenShader/Nodes/SourceCodeNode.cpp index 18ef93230a..5469b0cef1 100644 --- a/source/MaterialXGenShader/Nodes/SourceCodeNode.cpp +++ b/source/MaterialXGenShader/Nodes/SourceCodeNode.cpp @@ -81,17 +81,13 @@ void SourceCodeNode::emitFunctionCall(const ShaderNode& node, GenContext& contex static const string prefix("{{"); static const string postfix("}}"); - // Inline expressions can only have a single output - shader.beginLine(); - shadergen.emitOutput(context, node.getOutput(), true, false, shader); - shader.addStr(" = "); - size_t pos = 0; size_t i = _functionSource.find_first_of(prefix); + std::set variableNames; + vector code; while (i != string::npos) { - shader.addStr(_functionSource.substr(pos, i - pos)); - + code.push_back(_functionSource.substr(pos, i - pos)); size_t j = _functionSource.find_first_of(postfix, i + 2); if (j == string::npos) { @@ -105,13 +101,40 @@ void SourceCodeNode::emitFunctionCall(const ShaderNode& node, GenContext& contex throw ExceptionShaderGenError("Could not find an input named '" + variable + "' on node '" + node.getName() + "'"); } - shadergen.emitInput(context, input, shader); + + if (input->connection) + { + string inputStr; + shadergen.getInput(context, input, inputStr); + code.push_back(inputStr); + } + else + { + string variableName = node.getName() + "_" + input->name + "_tmp"; + if (!variableNames.count(variableName)) + { + Shader::Variable newVariable(input->type, variableName, EMPTY_STRING, EMPTY_STRING, input->value); + shader.beginLine(); + shadergen.emitVariable(newVariable, shadergen.getSyntax()->getConstantQualifier(), shader); + shader.endLine(); + variableNames.insert(variableName); + } + code.push_back(variableName); + } pos = j + 2; i = _functionSource.find_first_of(prefix, pos); } - shader.addStr(_functionSource.substr(pos)); - shader.endLine(); + code.push_back(_functionSource.substr(pos)); + code.push_back(ShaderGenerator::SEMICOLON_NEWLINE); + + shader.beginLine(); + shadergen.emitOutput(context, node.getOutput(), true, false, shader); + shader.addStr(" = "); + for (const string& c : code) + { + shader.addStr(c); + } } else { diff --git a/source/MaterialXGenShader/Nodes/SwizzleNode.cpp b/source/MaterialXGenShader/Nodes/SwizzleNode.cpp index a1b18a531f..9f96fd6523 100644 --- a/source/MaterialXGenShader/Nodes/SwizzleNode.cpp +++ b/source/MaterialXGenShader/Nodes/SwizzleNode.cpp @@ -31,15 +31,16 @@ void SwizzleNode::emitFunctionCall(const ShaderNode& node, GenContext& context, const string& swizzle = channels->value ? channels->value->getValueString() : EMPTY_STRING; string variableName = in->connection ? in->connection->variable : in->variable; + // If the input is unconnected we must declare a variable + // for it first, in order to swizzle it below. + if (!in->connection) + { + string variableValue = in->value ? shadergen.getSyntax()->getValue(in->type, *in->value) : shadergen.getSyntax()->getDefaultValue(in->type); + shader.addLine(shadergen.getSyntax()->getTypeName(in->type) + " " + variableName + " = " + variableValue); + } + if (!swizzle.empty()) { - // If the input is unconnected we must declare a variable - // for it first, in order to swizzle it below. - if (!in->connection) - { - string variableValue = in->value ? shadergen.getSyntax()->getValue(in->type, *in->value) : shadergen.getSyntax()->getDefaultValue(in->type); - shader.addLine(shadergen.getSyntax()->getTypeName(in->type) + " " + variableName + " = " + variableValue); - } const TypeDesc* type = in->connection ? in->connection->type : in->type; variableName = shadergen.getSyntax()->getSwizzledVariable(variableName, type, swizzle, node.getOutput()->type); } diff --git a/source/MaterialXGenShader/Shader.cpp b/source/MaterialXGenShader/Shader.cpp index 5ca9bb2946..4fc4267b05 100644 --- a/source/MaterialXGenShader/Shader.cpp +++ b/source/MaterialXGenShader/Shader.cpp @@ -12,17 +12,19 @@ namespace MaterialX { +const string Shader::PIXEL_STAGE = "pixel"; const string Shader::PRIVATE_UNIFORMS = "PrivateUniforms"; const string Shader::PUBLIC_UNIFORMS = "PublicUniforms"; Shader::Shader(const string& name) : _name(name) , _rootGraph(nullptr) - , _activeStage(PIXEL_STAGE) , _appData("AppData", "ad") , _outputs("Outputs", "op") { - _stages.push_back(Stage("Pixel")); + // Create pixel stage and make it the active stage + StagePtr pixelStage = createStage(PIXEL_STAGE); + _activeStage = pixelStage.get(); // Create default uniform blocks for pixel stage createUniformBlock(PIXEL_STAGE, PRIVATE_UNIFORMS, "prvUniform"); @@ -31,17 +33,6 @@ Shader::Shader(const string& name) void Shader::initialize(ElementPtr element, ShaderGenerator& shadergen, const GenOptions& options) { - // Check if validation step should be performed - if (options.validate) - { - string message; - bool valid = element->validate(&message); - if (!valid) - { - throw ExceptionShaderGenError("Element is invalid: " + message); - } - } - // Create our shader generation root graph _rootGraph = ShaderGraph::create(_name, element, shadergen, options); @@ -82,64 +73,96 @@ void Shader::initialize(ElementPtr element, ShaderGenerator& shadergen, const Ge } } +Shader::StagePtr Shader::createStage(const string& name) +{ + StagePtr s = std::make_shared(name); + _stages[name] = s; + return s; +} + +Shader::Stage* Shader::getStage(const string& name) +{ + auto it = _stages.find(name); + if (it == _stages.end()) + { + throw ExceptionShaderGenError("Stage '" + name + "' doesn't exist in shader '" + getName() + "'"); + } + return it->second.get(); +} + +const Shader::Stage* Shader::getStage(const string& name) const +{ + return const_cast(this)->getStage(name); +} + +void Shader::getStageNames(StringVec& stages) +{ + stages.reserve(_stages.size()); + for (auto it : _stages) + { + stages.push_back(it.first); + } +} + +void Shader::setActiveStage(const string& name) +{ + _activeStage = getStage(name); +} + void Shader::beginScope(Brackets brackets) { - Stage& s = stage(); - switch (brackets) { case Brackets::BRACES: indent(); - s.code += "{\n"; + _activeStage->code += "{\n"; break; case Brackets::PARENTHESES: indent(); - s.code += "(\n"; + _activeStage->code += "(\n"; break; case Brackets::SQUARES: indent(); - s.code += "[\n"; + _activeStage->code += "[\n"; break; case Brackets::NONE: break; } - ++s.indentations; - s.scopes.push(brackets); + ++_activeStage->indentations; + _activeStage->scopes.push(brackets); } void Shader::endScope(bool semicolon, bool newline) { - Stage& s = stage(); - - if (s.scopes.empty()) + if (_activeStage->scopes.empty()) { throw ExceptionShaderGenError("End scope called with no scope active, please check your beginScope/endScope calls"); } - Brackets brackets = s.scopes.back(); - s.scopes.pop(); - --s.indentations; + Brackets brackets = _activeStage->scopes.back(); + _activeStage->scopes.pop(); + --_activeStage->indentations; switch (brackets) { case Brackets::BRACES: indent(); - s.code += "}"; + _activeStage->code += "}"; break; case Brackets::PARENTHESES: indent(); - s.code += ")"; + _activeStage->code += ")"; break; case Brackets::SQUARES: indent(); - s.code += "]"; + _activeStage->code += "]"; break; case Brackets::NONE: break; } if (semicolon) - s.code += ";"; + _activeStage->code += ";"; if (newline) - s.code += "\n"; + _activeStage->code += "\n"; } void Shader::beginLine() @@ -149,30 +172,30 @@ void Shader::beginLine() void Shader::endLine(bool semicolon) { - stage().code += semicolon ? ";\n" : "\n"; + _activeStage->code += semicolon ? ";\n" : "\n"; } void Shader::newLine() { - stage().code += "\n"; + _activeStage->code += "\n"; } void Shader::addStr(const string& str) { - stage().code += str; + _activeStage->code += str; } void Shader::addLine(const string& str, bool semicolon) { beginLine(); - stage().code += str; + _activeStage->code += str; endLine(semicolon); } void Shader::addComment(const string& str) { beginLine(); - stage().code += "// " + str; + _activeStage->code += "// " + str; endLine(false); } @@ -210,11 +233,10 @@ void Shader::addBlock(const string& str, ShaderGenerator& shadergen) void Shader::addFunctionDefinition(ShaderNode* node, ShaderGenerator& shadergen) { - Stage& s = stage(); ShaderNodeImpl* impl = node->getImplementation(); - if (s.definedFunctions.find(impl) == s.definedFunctions.end()) + if (_activeStage->definedFunctions.find(impl) == _activeStage->definedFunctions.end()) { - s.definedFunctions.insert(impl); + _activeStage->definedFunctions.insert(impl); impl->emitFunctionDefinition(*node, shadergen, *this); } } @@ -229,15 +251,14 @@ void Shader::addInclude(const string& file, ShaderGenerator& shadergen) { const string path = shadergen.findSourceCode(file); - Stage& s = stage(); - if (s.includes.find(path) == s.includes.end()) + if (_activeStage->includes.find(path) == _activeStage->includes.end()) { string content; if (!readFile(path, content)) { throw ExceptionShaderGenError("Could not find include file: '" + file + "'"); } - s.includes.insert(path); + _activeStage->includes.insert(path); addBlock(content, shadergen); } } @@ -246,45 +267,48 @@ void Shader::indent() { // Use 4 spaces as default indentation static const string INDENTATION = " "; - Stage& s = stage(); - for (int i = 0; i < s.indentations; ++i) + for (int i = 0; i < _activeStage->indentations; ++i) { - s.code += INDENTATION; + _activeStage->code += INDENTATION; } } -void Shader::createConstant(size_t stage, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) +void Shader::createConstant(const string& stage, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) { - Stage& s = _stages[stage]; - if (s.constants.variableMap.find(name) == s.constants.variableMap.end()) + Stage* s = getStage(stage); + if (s->constants.variableMap.find(name) == s->constants.variableMap.end()) { VariablePtr variablePtr = Variable::create(type, name, path, semantic, value); - s.constants.variableMap[name] = variablePtr; - s.constants.variableOrder.push_back(variablePtr.get()); + s->constants.variableMap[name] = variablePtr; + s->constants.variableOrder.push_back(variablePtr.get()); } } -const Shader::VariableBlock& Shader::getConstantBlock(size_t stage) const +const Shader::VariableBlock& Shader::getConstantBlock(const string& stage) const +{ + return getStage(stage)->constants; +} + +const Shader::VariableBlockMap& Shader::getUniformBlocks(const string& stage) const { - const Stage& s = _stages[stage]; - return s.constants; + return getStage(stage)->uniforms; } -void Shader::createUniformBlock(size_t stage, const string& block, const string& instance) +void Shader::createUniformBlock(const string& stage, const string& block, const string& instance) { - Stage& s = _stages[stage]; - auto it = s.uniforms.find(block); - if (it == s.uniforms.end()) + Stage* s = getStage(stage); + auto it = s->uniforms.find(block); + if (it == s->uniforms.end()) { - s.uniforms[block] = std::make_shared(block, instance); + s->uniforms[block] = std::make_shared(block, instance); } } -void Shader::createUniform(size_t stage, const string& block, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) +void Shader::createUniform(const string& stage, const string& block, const TypeDesc* type, const string& name, const string& path, const string& semantic, ValuePtr value) { - const Stage& s = _stages[stage]; - auto it = s.uniforms.find(block); - if (it == s.uniforms.end()) + Stage* s = getStage(stage); + auto it = s->uniforms.find(block); + if (it == s->uniforms.end()) { throw ExceptionShaderGenError("No uniform block named '" + block + "' exists for shader '" + getName() + "'"); } @@ -297,11 +321,11 @@ void Shader::createUniform(size_t stage, const string& block, const TypeDesc* ty } } -const Shader::VariableBlock& Shader::getUniformBlock(size_t stage, const string& block) const +const Shader::VariableBlock& Shader::getUniformBlock(const string& stage, const string& block) const { - const Stage& s = _stages[stage]; - auto it = s.uniforms.find(block); - if (it == s.uniforms.end()) + const Stage* s = getStage(stage); + auto it = s->uniforms.find(block); + if (it == s->uniforms.end()) { throw ExceptionShaderGenError("No uniform block named '" + block + "' exists for shader '" + getName() + "'"); } diff --git a/source/MaterialXGenShader/Shader.h b/source/MaterialXGenShader/Shader.h index 5551995296..cdc5eea2bb 100644 --- a/source/MaterialXGenShader/Shader.h +++ b/source/MaterialXGenShader/Shader.h @@ -13,8 +13,8 @@ /// Macro for being/end of statements to be picked up by a given shader stage. /// For shaders that are multi-stage all code generation statements adding code /// to the shader should be wrapped inside such begin/end stating its target. -#define BEGIN_SHADER_STAGE(shader, stageId) if (shader.getActiveStage() == stageId) { -#define END_SHADER_STAGE(shader, stageId) } +#define BEGIN_SHADER_STAGE(shader, stage) if (shader.getActiveStage() == stage) { +#define END_SHADER_STAGE(shader, stage) } namespace MaterialX { @@ -111,8 +111,7 @@ class Shader /// Identifier for shader stages. The base class shader has only a single /// pixel shader stage. Derived shader classes can define additional stages. - static const size_t PIXEL_STAGE = 0; - static const size_t NUM_STAGES = 1; + static const string PIXEL_STAGE; /// Identifiers for uniform variable blocks. /// Derived classes can define additional blocks. @@ -138,38 +137,40 @@ class Shader virtual void initialize(ElementPtr element, ShaderGenerator& shadergen, const GenOptions& options); /// Return the number of shader stages for this shader. - /// Defaults to a single stage, derived classes can override this. - virtual size_t numStages() const { return NUM_STAGES; } + size_t numStages() const { return _stages.size(); } + + /// Return the name of the shader stages for this shader. + void getStageNames(StringVec& stages); /// Set the active stage that code will be added to - virtual void setActiveStage(size_t stage) { _activeStage = stage; } + void setActiveStage(const string& stage); - /// Return the active stage - virtual size_t getActiveStage() const { return _activeStage; } + /// Return the name of the active stage + const string& getActiveStage() const { return _activeStage->name; } /// Create a new constant variable for a stage. - virtual void createConstant(size_t stage, const TypeDesc* type, const string& name, + virtual void createConstant(const string& stage, const TypeDesc* type, const string& name, const string& path = EMPTY_STRING, const string& semantic = EMPTY_STRING, ValuePtr value = nullptr); /// Create a new variable block for uniform inputs in a stage. - virtual void createUniformBlock(size_t stage, const string& block, const string& instance = EMPTY_STRING); + virtual void createUniformBlock(const string& stage, const string& block, const string& instance = EMPTY_STRING); /// Create a new variable for uniform data in the given block for a stage. /// The block must be previously created with createUniformBlock. - virtual void createUniform(size_t stage, const string& block, const TypeDesc* type, const string& name, + virtual void createUniform(const string& stage, const string& block, const TypeDesc* type, const string& name, const string& path = EMPTY_STRING, const string& semantic = EMPTY_STRING, ValuePtr value = nullptr); /// Create a new variable for application/geometric data (primvars). virtual void createAppData(const TypeDesc* type, const string& name, const string& semantic = EMPTY_STRING); /// Return the block of constant variables for a stage. - const VariableBlock& getConstantBlock(size_t stage) const; + const VariableBlock& getConstantBlock(const string& stage) const; /// Return all blocks of uniform variables for a stage. - const VariableBlockMap& getUniformBlocks(size_t stage) const { return _stages[stage].uniforms; } + const VariableBlockMap& getUniformBlocks(const string& stage) const; /// Return a specific block of uniform variables for a stage. - const VariableBlock& getUniformBlock(size_t stage, const string& block) const; + const VariableBlock& getUniformBlock(const string& stage, const string& block) const; /// Return the block of application data variables. const VariableBlock& getAppDataBlock() const { return _appData; } @@ -222,7 +223,7 @@ class Shader { std::stringstream str; str << value; - stage().code += str.str(); + _activeStage->code += str.str(); } /// Return the shader name @@ -242,7 +243,7 @@ class Shader bool hasClassification(unsigned int c) const { return getGraph()->hasClassification(c); } /// Return the final shader source code for a given shader stage - const string& getSourceCode(size_t stage = PIXEL_STAGE) const { return _stages[stage].code; } + const string& getSourceCode(const string& stage = PIXEL_STAGE) const { return getStage(stage)->code; } protected: @@ -268,8 +269,14 @@ class Shader Stage(const string& n) : name(n), indentations(0), constants("Constants", "cn") {} }; - /// Return the currently active stage - Stage& stage() { return _stages[_activeStage]; } + using StagePtr = std::shared_ptr; + + /// Create a new stage with given name. + StagePtr createStage(const string& name); + + /// Return the stage with the given name. + Stage* getStage(const string& name); + const Stage* getStage(const string& name) const; /// Add indentation on current line virtual void indent(); @@ -281,8 +288,8 @@ class Shader ShaderGraphPtr _rootGraph; vector _graphStack; - size_t _activeStage; - vector _stages; + Stage* _activeStage; + std::unordered_map _stages; // Block holding application/geometric input variables VariableBlock _appData; diff --git a/source/MaterialXGenShader/ShaderGenerator.cpp b/source/MaterialXGenShader/ShaderGenerator.cpp index 2cef9d6d79..047146d22b 100644 --- a/source/MaterialXGenShader/ShaderGenerator.cpp +++ b/source/MaterialXGenShader/ShaderGenerator.cpp @@ -14,6 +14,9 @@ namespace MaterialX { +string ShaderGenerator::SEMICOLON_NEWLINE = ";\n"; +string ShaderGenerator::COMMA = ","; + ShaderGenerator::ShaderGenerator(SyntaxPtr syntax) : _syntax(syntax) { @@ -108,29 +111,22 @@ void ShaderGenerator::emitFinalOutput(Shader& shader) const shader.addLine(outputSocket->variable + " = " + outputSocket->connection->variable); } -void ShaderGenerator::emitConstant(const Shader::Variable& constant, Shader& shader) -{ - emitVariable(constant, _syntax->getConstantQualifier(), shader); -} - -void ShaderGenerator::emitUniform(const Shader::Variable& uniform, Shader& shader) -{ - emitVariable(uniform, _syntax->getUniformQualifier(), shader); -} - void ShaderGenerator::emitVariable(const Shader::Variable& variable, const string& /*qualifier*/, Shader& shader) { const string initStr = (variable.value ? _syntax->getValue(variable.type, *variable.value, true) : _syntax->getDefaultValue(variable.type, true)); shader.addStr(_syntax->getTypeName(variable.type) + " " + variable.name + (initStr.empty() ? "" : " = " + initStr)); } -void ShaderGenerator::emitVariableBlock(const Shader::VariableBlock& block, const string& qualifier, Shader& shader) +void ShaderGenerator::emitVariableBlock(const Shader::VariableBlock& block, const string& qualifier, const string& separator, Shader& shader) { if (!block.empty()) { for (const Shader::Variable* variable : block.variableOrder) { + shader.beginLine(); emitVariable(*variable, qualifier, shader); + shader.addStr(separator); + shader.endLine(false); } shader.newLine(); } @@ -185,12 +181,12 @@ void ShaderGenerator::emitOutput(const GenContext& context, const ShaderOutput* } } -void ShaderGenerator::addNodeContextIDs(ShaderNode* node) const +void ShaderGenerator::addContextIDs(ShaderNode* node) const { node->addContextID(CONTEXT_DEFAULT); } -const GenContext* ShaderGenerator::getNodeContext(int id) const +const GenContext* ShaderGenerator::getContext(int id) const { auto it = _contexts.find(id); return it != _contexts.end() ? it->second.get() : nullptr; diff --git a/source/MaterialXGenShader/ShaderGenerator.h b/source/MaterialXGenShader/ShaderGenerator.h index 96f7ff8bd6..d1095475e6 100644 --- a/source/MaterialXGenShader/ShaderGenerator.h +++ b/source/MaterialXGenShader/ShaderGenerator.h @@ -50,12 +50,6 @@ class ShaderGenerator /// Emit the final output expression virtual void emitFinalOutput(Shader& shader) const; - /// Emit a shader constant input variable - virtual void emitConstant(const Shader::Variable& uniform, Shader& shader); - - /// Emit a shader uniform input variable - virtual void emitUniform(const Shader::Variable& uniform, Shader& shader); - /// Emit the connected variable name for an input, /// or constant value if the port is not connected virtual void emitInput(const GenContext& context, const ShaderInput* input, Shader& shader) const; @@ -68,16 +62,31 @@ class ShaderGenerator /// and default value assignment. virtual void emitOutput(const GenContext& context, const ShaderOutput* output, bool includeType, bool assignDefault, Shader& shader) const; + /// Utility to emit a block of either uniform or constant variables + /// @param block Block to emit. + /// @param qualifier Optional qualifier to add before the variable declaration. + /// Qualifiers are specified by the syntax for the generator. + /// @param separator Separator to use between variables. + /// @param shader Shader to emit to. + virtual void emitVariableBlock(const Shader::VariableBlock& block, const string& qualifier, const string& separator, Shader& shader); + + /// Emit a shader input variable + /// @param variable Variable to emit + /// @param qualifier Optional qualifier to add before the variable declaration. + /// Qualifiers are specified by the syntax for the generator. + /// @param shader Shader source to emit output to + virtual void emitVariable(const Shader::Variable& variable, const string& qualifier, Shader& shader); + /// Return the syntax object for the language used by the code generator const Syntax* getSyntax() const { return _syntax.get(); } - /// Add node contexts id's to the given node to control + /// Add context id's to the given node to control /// in which contexts this node should be used. - virtual void addNodeContextIDs(ShaderNode* node) const; + virtual void addContextIDs(ShaderNode* node) const; - /// Return the node context corresponding to the given id, + /// Return the context corresponding to the given id, /// or nullptr if no such context is found. - const GenContext* getNodeContext(int id) const; + const GenContext* getContext(int id) const; template using CreatorFunction = shared_ptr(*)(); @@ -162,6 +171,9 @@ class ShaderGenerator CONTEXT_DEFAULT = 0 }; + static string SEMICOLON_NEWLINE; + static string COMMA; + protected: /// Protected constructor ShaderGenerator(SyntaxPtr syntax); @@ -180,20 +192,6 @@ class ShaderGenerator /// shader generators node context storage and returned. GenContextPtr createContext(int id); - /// Utility to emit a block of either uniform or constant variables - /// @param block Block to emit. - /// @param qualifier Optional qualifier to add before the variable declaration. - /// Qualifiers are specified by the syntax for the generator. - /// @param shader Shader to emit to. - virtual void emitVariableBlock(const Shader::VariableBlock& block, const string& qualifier, Shader& shader); - - /// Emit a shader input variable - /// @param variable Variable to emit - /// @param qualifier Optional qualifier to add before the variable declaration. - /// Qualifiers are specified by the syntax for the generator. - /// @param shader Shader source to emit output to - virtual void emitVariable(const Shader::Variable& variable, const string& qualifier, Shader& shader); - SyntaxPtr _syntax; Factory _implFactory; std::unordered_map _cachedImpls; diff --git a/source/MaterialXGenShader/ShaderGraph.cpp b/source/MaterialXGenShader/ShaderGraph.cpp index d8867f7507..1e896b4fff 100644 --- a/source/MaterialXGenShader/ShaderGraph.cpp +++ b/source/MaterialXGenShader/ShaderGraph.cpp @@ -603,15 +603,9 @@ ShaderNode* ShaderGraph::addNode(const Node& node, ShaderGenerator& shadergen, c } ColorManagementSystemPtr colorManagementSystem = shadergen.getColorManagementSystem(); - string targetColorSpace; - if (options.targetColorSpaceOverride.empty()) - { - targetColorSpace = _document ? _document->getActiveColorSpace() : EMPTY_STRING; - } - else - { - targetColorSpace = options.targetColorSpaceOverride; - } + const string& targetColorSpace = options.targetColorSpaceOverride.empty() ? + _document->getActiveColorSpace() : options.targetColorSpaceOverride; + if (colorManagementSystem && !targetColorSpace.empty()) { for (InputPtr input : node.getInputs()) diff --git a/source/MaterialXGenShader/ShaderNode.cpp b/source/MaterialXGenShader/ShaderNode.cpp index 465dac39cd..3682c491d0 100644 --- a/source/MaterialXGenShader/ShaderNode.cpp +++ b/source/MaterialXGenShader/ShaderNode.cpp @@ -303,7 +303,7 @@ ShaderNodePtr ShaderNode::create(const string& name, const NodeDef& nodeDef, Sha newNode->_classification |= groupClassification; // Let the shader generator assign in which contexts to use this node - shadergen.addNodeContextIDs(newNode.get()); + shadergen.addContextIDs(newNode.get()); return newNode; } @@ -395,7 +395,7 @@ ShaderNodePtr ShaderNode::createColorTransformNode(const string& name, ShaderNod throw ExceptionShaderGenError("Invalid type specified to createColorTransform: '" + type->getName() + "'"); } newNode->addOutput("out", type); - shadergen.addNodeContextIDs(newNode.get()); + shadergen.addContextIDs(newNode.get()); return newNode; } diff --git a/source/MaterialXGenShader/Syntax.cpp b/source/MaterialXGenShader/Syntax.cpp index f705cf3dca..2ce5e92b49 100644 --- a/source/MaterialXGenShader/Syntax.cpp +++ b/source/MaterialXGenShader/Syntax.cpp @@ -191,7 +191,11 @@ string ScalarTypeSyntax::getValue(const vector& values, bool /*uniform*/ { throw ExceptionShaderGenError("No values given to construct a value"); } - return values[0]; + // Write the value using a stream to maintain any float formatting set + // using Value::setFloatFormat() and Value::setFloatPrecision() + std::stringstream ss; + ss << values[0]; + return ss.str(); } @@ -224,14 +228,17 @@ string AggregateTypeSyntax::getValue(const vector& values, bool /*unifor throw ExceptionShaderGenError("No values given to construct a value"); } - string result = getName() + "(" + values[0]; + // Write the value using a stream to maintain any float formatting set + // using Value::setFloatFormat() and Value::setFloatPrecision() + std::stringstream ss; + ss << getName() << "(" << values[0]; for (size_t i=1; i 0) + if (stream) { - size_t pos = buffer.find_last_not_of('\0'); - contents = buffer.substr(0, pos + 1); + contents = stream.str(); + return (contents.size() > 0); } - result = true; + return false; } -#if defined(_WIN32) - _set_fmode(oldMode ? oldMode : _O_TEXT); -#endif - - return result; + return false; } string getFileExtension(const string& filename) @@ -298,6 +282,8 @@ namespace // Second check the opacity if (opaque) { + opaque = false; + InputPtr opacity = node->getInput("opacity"); if (!opacity) { @@ -406,6 +392,8 @@ bool isTransparentSurface(ElementPtr element, const ShaderGenerator& shadergen) // Second check the opacity if (opaque) { + opaque = false; + BindInputPtr opacity = shaderRef->getBindInput("opacity"); if (!opacity) { @@ -415,7 +403,7 @@ bool isTransparentSurface(ElementPtr element, const ShaderGenerator& shadergen) { // Unconnected, check the value ValuePtr value = opacity->getValue(); - if (!value || isWhite(value->asA())) + if (!value || (value->isA() && isWhite(value->asA()))) { opaque = true; } @@ -759,4 +747,22 @@ unsigned int getUIProperties(const string& path, DocumentPtr doc, const string& return 0; } +void mapNodeDefToIdentiers(const std::vector& nodes, + std::unordered_map& ids) +{ + unsigned int id = 1; + for (auto node : nodes) + { + auto nodedef = node->getNodeDef(); + if (nodedef) + { + const std::string& name = nodedef->getName(); + if (!ids.count(name)) + { + ids[name] = id++; + } + } + } +} + } // namespace MaterialX diff --git a/source/MaterialXGenShader/Util.h b/source/MaterialXGenShader/Util.h index 621df65dcb..4a70c5be54 100644 --- a/source/MaterialXGenShader/Util.h +++ b/source/MaterialXGenShader/Util.h @@ -69,6 +69,10 @@ void findRenderableElements(const DocumentPtr& doc, std::vector /// if the path is to a Node as definitions for Nodes can be target specific. ValueElementPtr findNodeDefChild(const string& path, DocumentPtr doc, const string& target); +// From a set of nodes, create a mapping of nodedef identifiers to numbers +void mapNodeDefToIdentiers(const std::vector& nodes, + std::unordered_map& ids); + /// Set of possible UI properties for an element struct UIProperties { diff --git a/source/MaterialXRender/Handlers/GeometryHandler.cpp b/source/MaterialXRender/Handlers/GeometryHandler.cpp index 998cc85fa3..4eb0e621c1 100644 --- a/source/MaterialXRender/Handlers/GeometryHandler.cpp +++ b/source/MaterialXRender/Handlers/GeometryHandler.cpp @@ -63,11 +63,11 @@ void GeometryHandler::computeBounds() const Vector3& minMesh = mesh->getMinimumBounds(); _minimumBounds[0] = std::min(minMesh[0], _minimumBounds[0]); _minimumBounds[1] = std::min(minMesh[1], _minimumBounds[1]); - _minimumBounds[2] = std::min(minMesh[1], _minimumBounds[2]); + _minimumBounds[2] = std::min(minMesh[2], _minimumBounds[2]); const Vector3& maxMesh = mesh->getMaximumBounds(); _maximumBounds[0] = std::max(maxMesh[0], _maximumBounds[0]); _maximumBounds[1] = std::max(maxMesh[1], _maximumBounds[1]); - _maximumBounds[2] = std::max(maxMesh[1], _maximumBounds[2]); + _maximumBounds[2] = std::max(maxMesh[2], _maximumBounds[2]); } } diff --git a/source/MaterialXRender/Handlers/HwLightHandler.cpp b/source/MaterialXRender/Handlers/HwLightHandler.cpp new file mode 100644 index 0000000000..1a046810d5 --- /dev/null +++ b/source/MaterialXRender/Handlers/HwLightHandler.cpp @@ -0,0 +1,19 @@ +#include + +namespace MaterialX +{ + +HwLightHandler::HwLightHandler() +{ +} + +HwLightHandler::~HwLightHandler() +{ +} + +void HwLightHandler::addLightSource(NodePtr node) +{ + _lightSources.push_back(node); +} + +} diff --git a/source/MaterialXGenShader/HwLightHandler.h b/source/MaterialXRender/Handlers/HwLightHandler.h similarity index 78% rename from source/MaterialXGenShader/HwLightHandler.h rename to source/MaterialXRender/Handlers/HwLightHandler.h index d269265fdb..2870098ba2 100644 --- a/source/MaterialXGenShader/HwLightHandler.h +++ b/source/MaterialXRender/Handlers/HwLightHandler.h @@ -10,9 +10,6 @@ namespace MaterialX { -class HwShaderGenerator; -class GenOptions; - /// Shared pointer to a LightHandler using HwLightHandlerPtr = std::shared_ptr; @@ -26,8 +23,6 @@ class HwLightHandler /// Static instance create function static HwLightHandlerPtr create() { return std::make_shared(); } - static unsigned int getLightType(NodePtr node); - /// Default constructor HwLightHandler(); @@ -37,16 +32,17 @@ class HwLightHandler // Adds a light source node void addLightSource(NodePtr node); - /// Return a vector of all created light sources. + /// Get the list of light sources. const vector& getLightSources() const { return _lightSources; } - /// Bind all added light shaders to the given shader generator. - /// Only the light shaders bound to the generator will have their - /// code emitted during shader generation. - void bindLightShaders(HwShaderGenerator& shadergen, const GenOptions& options) const; + /// Set the list of light sources. + void setLightSources(const vector& lights) + { + _lightSources = lights; + } /// Set path to irradiance IBL image void setLightEnvIrradiancePath(const string& path) diff --git a/source/MaterialXRender/Handlers/TestObjLoader.cpp b/source/MaterialXRender/Handlers/TestObjLoader.cpp index c338c1fc21..ccb8d9504e 100644 --- a/source/MaterialXRender/Handlers/TestObjLoader.cpp +++ b/source/MaterialXRender/Handlers/TestObjLoader.cpp @@ -67,8 +67,7 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) // Enable debugging of read by dumping to disk what was read in. // Disabled by default std::ofstream dump; - bool debugDump = false; - if (debugDump) + if (_debugDump) { dump.open("dump.obj"); } @@ -84,7 +83,7 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) std::istringstream valstring(line.substr(2)); valstring >> val1; valstring >> val2; valstring >> val3; - if (debugDump) + if (_debugDump) { dump << "v " << val1 << " " << val2 << " " << val3 << std::endl; } @@ -108,7 +107,7 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) valstring >> val1; valstring >> val2; uv.push_back(val1); uv.push_back(val2); - if (debugDump) + if (_debugDump) { dump << "vt " << val1 << " " << val2 << std::endl; } @@ -119,7 +118,7 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) valstring >> val1; valstring >> val2; valstring >> val3; norm.push_back(val1); norm.push_back(val2); norm.push_back(val3); - if (debugDump) + if (_debugDump) { dump << "vn " << val1 << " " << val2 << " " << val3 << std::endl; } @@ -169,10 +168,9 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) partitions.push_back(currentPartition); } - size_t facesAdded = 0; if (vertexCount >= 3) { - facesAdded++; + size_t facesAdded = 1; pidx.push_back(ipos[0] - 1); pidx.push_back(ipos[1] - 1); @@ -186,7 +184,7 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) nidx.push_back(inorm[1] - 1); nidx.push_back(inorm[2] - 1); - if (debugDump) + if (_debugDump) { dump << "f " << ipos[0] << "/" << iuv[0] << "/" << inorm[0] << " " @@ -210,7 +208,7 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) nidx.push_back(inorm[2] - 1); nidx.push_back(inorm[3] - 1); - if (debugDump) + if (_debugDump) { dump << "f " << ipos[0] << "/" << iuv[0] << "/" << inorm[0] << " " @@ -225,7 +223,10 @@ bool TestObjLoader::load(const std::string& fileName, MeshList& meshList) } } - dump.close(); + if (_debugDump) + { + dump.close(); + } objfile.close(); // Set bounds diff --git a/source/MaterialXRender/Handlers/TestObjLoader.h b/source/MaterialXRender/Handlers/TestObjLoader.h index 5e376f8288..c5ee57fa0c 100644 --- a/source/MaterialXRender/Handlers/TestObjLoader.h +++ b/source/MaterialXRender/Handlers/TestObjLoader.h @@ -21,7 +21,8 @@ class TestObjLoader : public GeometryLoader /// Default constructor TestObjLoader() : - _readGroups(true) + _readGroups(true), + _debugDump(false) { _extensions = { "obj", "OBJ" }; } @@ -46,6 +47,7 @@ class TestObjLoader : public GeometryLoader protected: bool _readGroups; + bool _debugDump; }; } // namespace MaterialX diff --git a/source/MaterialXRender/OpenGL/GLCocoaWrappers.m b/source/MaterialXRender/OpenGL/GLCocoaWrappers.m index 52d172428b..31d3fbbeb8 100644 --- a/source/MaterialXRender/OpenGL/GLCocoaWrappers.m +++ b/source/MaterialXRender/OpenGL/GLCocoaWrappers.m @@ -179,7 +179,7 @@ void NSOpenGLDescribePixelFormat(void* pPixelFormat, int attrib, int* vals) void NSOpenGLGetInteger(void* pContext, int param, int* vals) { NSOpenGLContext* context = (NSOpenGLContext*)pContext; - [context getValues:vals forParameter:param]; + [context getValues:vals forParameter:(NSOpenGLContextParameter)param]; } void NSOpenGLUpdate(void* pContext) diff --git a/source/MaterialXRender/OpenGL/GlslProgram.cpp b/source/MaterialXRender/OpenGL/GlslProgram.cpp index 772420e6d2..d9d4612061 100644 --- a/source/MaterialXRender/OpenGL/GlslProgram.cpp +++ b/source/MaterialXRender/OpenGL/GlslProgram.cpp @@ -15,7 +15,7 @@ int GlslProgram::UNDEFINED_OPENGL_PROGRAM_LOCATION = -1; int GlslProgram::Input::INVALID_OPENGL_TYPE = -1; // Shader constants -static string RADIANCE_ENV_UNIFORM_NAME("u_envSpecular"); +static string RADIANCE_ENV_UNIFORM_NAME("u_envRadiance"); static string IRRADIANCE_ENV_UNIFORM_NAME("u_envIrradiance"); /// Sampling constants @@ -48,26 +48,6 @@ GlslProgram::~GlslProgram() deleteProgram(); } -void GlslProgram::setStages(const std::vector& stages) -{ - if (stages.size() != HwShader::NUM_STAGES) - { - ShaderValidationErrorList errors; - throw ExceptionShaderValidationError("Incorrect number of stages passed in for stage setup.", errors); - } - - unsigned int count = 0; - for (auto stage : stages) - { - _stages[count++] = stage; - } - - // A stage change invalidates any cached parsed inputs - clearInputLists(); - _hwShader = nullptr; -} - - void GlslProgram::setStages(const HwShaderPtr shader) { if (!shader) @@ -81,42 +61,38 @@ void GlslProgram::setStages(const HwShaderPtr shader) // Extract out the shader code per stage _hwShader = shader; - for (size_t i = 0; i < HwShader::NUM_STAGES; i++) + StringVec stages; + shader->getStageNames(stages); + for (const string& s : stages) { - _stages[i] = _hwShader->getSourceCode(i); + addStage(s, shader->getSourceCode(s)); } // A stage change invalidates any cached parsed inputs clearInputLists(); } -const std::string GlslProgram::getStage(size_t stage) const +void GlslProgram::addStage(const string& stage, const string& sourcCode) { - if (stage < HwShader::NUM_STAGES) - { - return _stages[stage]; - } - return std::string(); + _stages[stage] = sourcCode; } -void GlslProgram::clearStages() +const string& GlslProgram::getStageSourceCode(const string& stage) const { - for (size_t i = 0; i < HwShader::NUM_STAGES; i++) + auto it = _stages.find(stage); + if (it != _stages.end()) { - _stages[i].clear(); + return it->second; } - - // Clearing stages invalidates any cached inputs - clearInputLists(); + return EMPTY_STRING; } -bool GlslProgram::haveValidStages() const +void GlslProgram::clearStages() { - // Need at least a pixel shader stage and a vertex shader stage - const std::string& vertexShaderSource = _stages[HwShader::VERTEX_STAGE]; - const std::string& fragmentShaderSource = _stages[HwShader::PIXEL_STAGE]; + _stages.clear(); - return (vertexShaderSource.length() > 0 && fragmentShaderSource.length() > 0); + // Clearing stages invalidates any cached inputs + clearInputLists(); } void GlslProgram::deleteProgram() @@ -139,20 +115,14 @@ unsigned int GlslProgram::build() deleteProgram(); - if (!haveValidStages()) - { - errors.push_back("An invalid set of stages has been provided."); - throw ExceptionShaderValidationError(errorType, errors); - } - GLint GLStatus = GL_FALSE; int GLInfoLogLength = 0; unsigned int stagesBuilt = 0; unsigned int desiredStages = 0; - for (unsigned int i = 0; i < HwShader::NUM_STAGES; i++) + for (auto it : _stages) { - if (_stages[i].length()) + if (it.second.length()) desiredStages++; } @@ -243,6 +213,11 @@ unsigned int GlslProgram::build() } } } + else + { + errors.push_back("Failed to build all stages."); + throw ExceptionShaderValidationError(errorType, errors); + } // Cleanup if (vertexShaderId > UNDEFINED_OPENGL_RESOURCE_ID) @@ -691,7 +666,9 @@ void GlslProgram::bindLighting(HwLightHandlerPtr lightHandler, ImageHandlerPtr i lightCount = 0; } - if (lightCount == 0) + if (lightCount == 0 && + lightHandler->getLightEnvRadiancePath().empty() && + lightHandler->getLightEnvIrradiancePath().empty()) { return; } @@ -722,23 +699,49 @@ void GlslProgram::bindLighting(HwLightHandlerPtr lightHandler, ImageHandlerPtr i } } + const std::vector lightList = lightHandler->getLightSources(); + std::unordered_map ids; + mapNodeDefToIdentiers(lightList, ids); + size_t index = 0; - for (NodePtr light : lightHandler->getLightSources()) + for (auto light : lightList) { + auto nodeDef = light->getNodeDef(); const string prefix = "u_lightData[" + std::to_string(index) + "]"; // Set light type id + bool boundType = false; input = uniformList.find(prefix + ".type"); if (input != uniformList.end()) { location = input->second->location; if (location >= 0) { - glUniform1i(location, int(HwLightHandler::getLightType(light))); + unsigned int lightType = ids[nodeDef->getName()]; + glUniform1i(location, lightType); + boundType = true; + } + } + if (!boundType) + { + continue; + } + + // Set all inputs + for (auto lightInput : light->getInputs()) + { + // Make sure we have a value to set + if (lightInput->hasValue()) + { + input = uniformList.find(prefix + "." + lightInput->getName()); + if (input != uniformList.end()) + { + bindUniform(input->second->location, *lightInput->getValue()); + } } } - // Set all parameters + // Set all parameters. Note that upstream connections are not currently handled. for (auto param : light->getParameters()) { // Make sure we have a value to set diff --git a/source/MaterialXRender/OpenGL/GlslProgram.h b/source/MaterialXRender/OpenGL/GlslProgram.h index 5746d6a34e..c2cf5883f9 100644 --- a/source/MaterialXRender/OpenGL/GlslProgram.h +++ b/source/MaterialXRender/OpenGL/GlslProgram.h @@ -2,11 +2,11 @@ #define MATERIALX_GLSLPROGRAM_H #include -#include #include #include #include #include +#include #include #include #include @@ -44,19 +44,13 @@ class GlslProgram /// Set the code stages based on a list of stage strings. /// Refer to the ordering of stages as defined by a HwShader. - /// @param stages List of shader code strings. - void setStages(const std::vector& stages); + /// @param stage Name of the shader stage. + /// @param sourcCode Source code of the shader stage. + void addStage(const string& stage, const string& sourcCode); - /// Get code string for a given stage + /// Get source code string for a given stage. /// @return Shader stage string. String is empty if not found. - const std::string getStage(size_t stage) const; - - /// Get the number of stages - /// @return Stage count - size_t numStages() const - { - return HwShader::NUM_STAGES; - } + const string& getStageSourceCode(const string& stage) const; /// Clear out any existing stages void clearStages(); @@ -232,12 +226,6 @@ class GlslProgram bool bindTexture(unsigned int uniformType, int uniformLocation, const string& fileName, ImageHandlerPtr imageHandler, bool generateMipMaps, const ImageSamplingProperties& imageProperties); - /// Internal cleanup of stages and OpenGL constructs - void cleanup(); - - /// Check if there is a valid set of stages to build program from - bool haveValidStages() const; - /// Utility to check for OpenGL context errors. /// Will throw an ExceptionShaderValidationError exception which will list of the errors found /// if any errors encountered. @@ -250,7 +238,8 @@ class GlslProgram private: /// Stages used to create program - std::string _stages[HwShader::NUM_STAGES]; + /// Map of stage name and its source code + std::unordered_map _stages; /// Generated program. A non-zero number indicates a valid shader program. unsigned int _programId; diff --git a/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.cpp b/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.cpp index 1cc2353b2c..5cd9e190a4 100644 --- a/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.cpp +++ b/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.cpp @@ -62,7 +62,7 @@ GlslValidator::~GlslValidator() void GlslValidator::initialize() { ShaderValidationErrorList errors; - const std::string errorType("OpenGL utilities initialization."); + const string errorType("OpenGL utilities initialization."); if (!_initialized) { @@ -132,7 +132,7 @@ void GlslValidator::deleteTarget() bool GlslValidator::createTarget() { ShaderValidationErrorList errors; - const std::string errorType("OpenGL target creation failure."); + const string errorType("OpenGL target creation failure."); if (!_context) { @@ -200,7 +200,7 @@ bool GlslValidator::createTarget() glDeleteFramebuffers(1, &_frameBuffer); _frameBuffer = MaterialX::GlslProgram::UNDEFINED_OPENGL_RESOURCE_ID; - std::string errorMessage("Frame buffer object setup failed: "); + string errorMessage("Frame buffer object setup failed: "); switch (status) { case GL_FRAMEBUFFER_COMPLETE: errorMessage += "GL_FRAMEBUFFER_COMPLETE"; @@ -275,7 +275,7 @@ bool GlslValidator::bindTarget(bool bind) void GlslValidator::validateCreation(const ShaderPtr shader) { ShaderValidationErrorList errors; - const std::string errorType("GLSL program creation error."); + const string errorType("GLSL program creation error."); if (!_context) { @@ -293,10 +293,10 @@ void GlslValidator::validateCreation(const ShaderPtr shader) _program->build(); } -void GlslValidator::validateCreation(const std::vector& stages) +void GlslValidator::validateCreation(const StageMap& stages) { ShaderValidationErrorList errors; - const std::string errorType("GLSL program creation error."); + const string errorType("GLSL program creation error."); if (!_context) { @@ -310,14 +310,17 @@ void GlslValidator::validateCreation(const std::vector& stages) throw ExceptionShaderValidationError(errorType, errors); } - _program->setStages(stages); + for (auto it : stages) + { + _program->addStage(it.first, it.second); + } _program->build(); } void GlslValidator::validateInputs() { ShaderValidationErrorList errors; - const std::string errorType("GLSL program input error."); + const string errorType("GLSL program input error."); if (!_context) { @@ -399,7 +402,7 @@ void GlslValidator::validateRender(bool orthographicView) _orthographicView = orthographicView; ShaderValidationErrorList errors; - const std::string errorType("GLSL rendering error."); + const string errorType("GLSL rendering error."); if (!_context) { @@ -470,20 +473,20 @@ void GlslValidator::validateRender(bool orthographicView) } } } - catch (ExceptionShaderValidationError& e) + catch (ExceptionShaderValidationError& /*e*/) { bindTarget(false); - throw e; + throw; } // Unset target bindTarget(false); } -void GlslValidator::save(const std::string& fileName, bool floatingPoint) +void GlslValidator::save(const string& fileName, bool floatingPoint) { ShaderValidationErrorList errors; - const std::string errorType("GLSL image save error."); + const string errorType("GLSL image save error."); if (!_imageHandler) { @@ -528,7 +531,7 @@ void GlslValidator::save(const std::string& fileName, bool floatingPoint) if (!saved) { - errors.push_back("Faled to save to file:" + fileName); + errors.push_back("Failed to save to file:" + fileName); throw ExceptionShaderValidationError(errorType, errors); } } diff --git a/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.h b/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.h index 874a5a455a..86eeb61e3f 100644 --- a/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.h +++ b/source/MaterialXRender/ShaderValidators/Glsl/GlslValidator.h @@ -57,9 +57,9 @@ class GlslValidator : public ShaderValidator /// @param shader Input HwShader void validateCreation(const ShaderPtr shader) override; - /// Validate creation of program based input shader stage strings - /// @param shader Input stages List of stage string - void validateCreation(const std::vector& stages) override; + /// Validate creation of program based on shader stage source code. + /// @param stages Map of name and source code for the shader stages. + void validateCreation(const StageMap& stages) override; /// Validate inputs for the program void validateInputs() override; @@ -76,7 +76,7 @@ class GlslValidator : public ShaderValidator /// Save the current contents the offscreen hardware buffer to disk. /// @param fileName Name of file to save rendered image to. /// @param floatingPoint Format of output image is floating point. - void save(const std::string& fileName, bool floatingPoint) override; + void save(const string& fileName, bool floatingPoint) override; /// Return the GLSL program wrapper class MaterialX::GlslProgramPtr program() diff --git a/source/MaterialXRender/ShaderValidators/Osl/OslValidator.cpp b/source/MaterialXRender/ShaderValidators/Osl/OslValidator.cpp index baf0ed40cd..7593ad5f53 100644 --- a/source/MaterialXRender/ShaderValidators/Osl/OslValidator.cpp +++ b/source/MaterialXRender/ShaderValidators/Osl/OslValidator.cpp @@ -11,7 +11,7 @@ namespace MaterialX { // Statics -std::string OslValidator::OSL_CLOSURE_COLOR_STRING("closure color"); +string OslValidator::OSL_CLOSURE_COLOR_STRING("closure color"); // // Creator @@ -34,7 +34,7 @@ OslValidator::~OslValidator() void OslValidator::initialize() { ShaderValidationErrorList errors; - const std::string errorType("OSL initialization error."); + const string errorType("OSL initialization error."); if (_oslIncludePathString.empty()) { errors.push_back("OSL validation include path is empty."); @@ -47,10 +47,10 @@ void OslValidator::initialize() } } -void OslValidator::renderOSL(const std::string& outputPath, const std::string& shaderName, const std::string& outputName) +void OslValidator::renderOSL(const string& outputPath, const string& shaderName, const string& outputName) { ShaderValidationErrorList errors; - const std::string errorType("OSL rendering error."); + const string errorType("OSL rendering error."); // If command options missing, skip testing. if (_oslTestRenderExecutable.empty() || _oslIncludePathString.empty() || @@ -76,37 +76,37 @@ void OslValidator::renderOSL(const std::string& outputPath, const std::string& s // Determine the shader path from output path and shader name FilePath shaderFilePath(outputPath); shaderFilePath = shaderFilePath / shaderName; - std::string shaderPath = shaderFilePath.asString(); + string shaderPath = shaderFilePath.asString(); // Set output image name. - std::string outputFileName = shaderPath + "_osl.png"; + string outputFileName = shaderPath + "_osl.png"; // Use a known error file name to check - std::string errorFile(shaderPath + "_render_errors.txt"); - const std::string redirectString(" 2>&1"); + string errorFile(shaderPath + "_render_errors.txt"); + const string redirectString(" 2>&1"); // Read in scene template and replace the applicable tokens to have a valid ShaderGroup. // Write to local file to use as input for rendering. // std::ifstream sceneTemplateStream(_oslTestRenderSceneTemplateFile); - std::string sceneTemplateString; + string sceneTemplateString; sceneTemplateString.assign(std::istreambuf_iterator(sceneTemplateStream), std::istreambuf_iterator()); // Get final output to use in the shader - const std::string CLOSURE_PASSTHROUGH_SHADER_STRING("closure_passthrough"); - const std::string CONSTANT_COLOR_SHADER_STRING("constant_color"); - const std::string CONSTANT_COLOR_SHADER_PREFIX_STRING("constant_"); - std::string outputShader = isColorClosure ? CLOSURE_PASSTHROUGH_SHADER_STRING : + const string CLOSURE_PASSTHROUGH_SHADER_STRING("closure_passthrough"); + const string CONSTANT_COLOR_SHADER_STRING("constant_color"); + const string CONSTANT_COLOR_SHADER_PREFIX_STRING("constant_"); + string outputShader = isColorClosure ? CLOSURE_PASSTHROUGH_SHADER_STRING : (isRemappable ? CONSTANT_COLOR_SHADER_PREFIX_STRING + _oslShaderOutputType : CONSTANT_COLOR_SHADER_STRING); // Perform token replacement - const std::string OUTPUT_SHADER_TYPE_STRING("%output_shader_type%"); - const std::string OUTPUT_SHADER_INPUT_STRING("%output_shader_input%"); - const std::string OUTPUT_SHADER_INPUT_VALUE_STRING("Cin"); - const std::string INPUT_SHADER_TYPE_STRING("%input_shader_type%"); - const std::string INPUT_SHADER_OUTPUT_STRING("%input_shader_output%"); - const std::string BACKGROUND_COLOR_STRING("%background_color%"); + const string OUTPUT_SHADER_TYPE_STRING("%output_shader_type%"); + const string OUTPUT_SHADER_INPUT_STRING("%output_shader_input%"); + const string OUTPUT_SHADER_INPUT_VALUE_STRING("Cin"); + const string INPUT_SHADER_TYPE_STRING("%input_shader_type%"); + const string INPUT_SHADER_OUTPUT_STRING("%input_shader_output%"); + const string BACKGROUND_COLOR_STRING("%background_color%"); const string backgroundColor("0.0 0.0 0.0"); // TODO: Make this a user input StringMap replacementMap; @@ -115,7 +115,7 @@ void OslValidator::renderOSL(const std::string& outputPath, const std::string& s replacementMap[INPUT_SHADER_TYPE_STRING] = shaderName; replacementMap[INPUT_SHADER_OUTPUT_STRING] = outputName; replacementMap[BACKGROUND_COLOR_STRING] = backgroundColor; - std::string sceneString = replaceSubstrings(sceneTemplateString, replacementMap); + string sceneString = replaceSubstrings(sceneTemplateString, replacementMap); if ((sceneString == sceneTemplateString) || sceneTemplateString.empty()) { errors.push_back("Scene template file: " + _oslTestRenderSceneTemplateFile + @@ -124,7 +124,7 @@ void OslValidator::renderOSL(const std::string& outputPath, const std::string& s } // Write scene file - const std::string sceneFileName(shaderPath + "_scene.xml"); + const string sceneFileName(shaderPath + "_scene.xml"); std::ofstream shaderFileStream; shaderFileStream.open(sceneFileName); if (shaderFileStream.is_open()) @@ -134,12 +134,12 @@ void OslValidator::renderOSL(const std::string& outputPath, const std::string& s } // Set oso file paths - std::string osoPaths(_oslUtilityOSOPath); + string osoPaths(_oslUtilityOSOPath); osoPaths += ";" + outputPath; // Build and run render command // - std::string command(_oslTestRenderExecutable); + string command(_oslTestRenderExecutable); command += " " + sceneFileName; command += " " + outputFileName; command += " -r 512 512 --path " + osoPaths; @@ -152,7 +152,7 @@ void OslValidator::renderOSL(const std::string& outputPath, const std::string& s int returnValue = std::system(command.c_str()); std::ifstream errorStream(errorFile); - std::string result; + string result; result.assign(std::istreambuf_iterator(errorStream), std::istreambuf_iterator()); @@ -166,7 +166,7 @@ void OslValidator::renderOSL(const std::string& outputPath, const std::string& s } } -void OslValidator::shadeOSL(const std::string& outputPath, const std::string& shaderName, const std::string& outputName) +void OslValidator::shadeOSL(const string& outputPath, const string& shaderName, const string& outputName) { // If no command and include path specified then skip checking. if (_oslTestShadeExecutable.empty() || _oslIncludePathString.empty()) @@ -176,16 +176,16 @@ void OslValidator::shadeOSL(const std::string& outputPath, const std::string& sh FilePath shaderFilePath(outputPath); shaderFilePath = shaderFilePath / shaderName; - std::string shaderPath = shaderFilePath.asString(); + string shaderPath = shaderFilePath.asString(); // Set output image name. - std::string outputFileName = shaderPath + ".testshade.png"; + string outputFileName = shaderPath + ".testshade.png"; // Use a known error file name to check - std::string errorFile(shaderPath + "_shade_errors.txt"); - const std::string redirectString(" 2>&1"); + string errorFile(shaderPath + "_shade_errors.txt"); + const string redirectString(" 2>&1"); - std::string command(_oslTestShadeExecutable); + string command(_oslTestShadeExecutable); command += " " + shaderPath; command += " -o " + outputName + " " + outputFileName; command += " -g 256 256"; @@ -199,15 +199,15 @@ void OslValidator::shadeOSL(const std::string& outputPath, const std::string& sh // modifies this then this hard-coded string must also be modified. // The formatted string is "Output to ". std::ifstream errorStream(errorFile); - std::string result; - std::vector results; - std::string line; - std::string successfulOutputSubString("Output " + outputName + " to " + + string result; + std::vector results; + string line; + string successfulOutputSubString("Output " + outputName + " to " + outputFileName); while (std::getline(errorStream, line)) { if (!line.empty() && - line.find(successfulOutputSubString) == std::string::npos) + line.find(successfulOutputSubString) == string::npos) { results.push_back(line); } @@ -215,7 +215,7 @@ void OslValidator::shadeOSL(const std::string& outputPath, const std::string& sh if (!results.empty()) { - const std::string errorType("OSL rendering error."); + const string errorType("OSL rendering error."); ShaderValidationErrorList errors; errors.push_back("Command string: " + command); errors.push_back("Command return code: " + std::to_string(returnValue)); @@ -228,7 +228,7 @@ void OslValidator::shadeOSL(const std::string& outputPath, const std::string& sh } } -void OslValidator::compileOSL(const std::string& oslFileName) +void OslValidator::compileOSL(const string& oslFileName) { // If no command and include path specified then skip checking. if (_oslCompilerExecutable.empty() || _oslIncludePathString.empty()) @@ -237,27 +237,27 @@ void OslValidator::compileOSL(const std::string& oslFileName) } // Remove .osl and add .oso extension for output. - std::string outputFileName = removeExtension(oslFileName); + string outputFileName = removeExtension(oslFileName); outputFileName += ".oso"; // Use a known error file name to check - std::string errorFile(oslFileName + "_compile_errors.txt"); - const std::string redirectString(" 2>&1"); + string errorFile(oslFileName + "_compile_errors.txt"); + const string redirectString(" 2>&1"); // Run the command and get back the result. If non-empty string throw exception with error - std::string command = _oslCompilerExecutable + " -q -I\"" + _oslIncludePathString + "\" " + oslFileName + " -o " + outputFileName + " > " + + string command = _oslCompilerExecutable + " -q -I\"" + _oslIncludePathString + "\" " + oslFileName + " -o " + outputFileName + " > " + errorFile + redirectString; int returnValue = std::system(command.c_str()); std::ifstream errorStream(errorFile); - std::string result; + string result; result.assign(std::istreambuf_iterator(errorStream), std::istreambuf_iterator()); if (!result.empty()) { - const std::string errorType("OSL compilation error."); + const string errorType("OSL compilation error."); ShaderValidationErrorList errors; errors.push_back("Command string: " + command); errors.push_back("Command return code: " + std::to_string(returnValue)); @@ -269,17 +269,17 @@ void OslValidator::compileOSL(const std::string& oslFileName) void OslValidator::validateCreation(const ShaderPtr shader) { - std::vector stages; - stages.push_back(shader->getSourceCode()); - + StageMap stages = { {Shader::PIXEL_STAGE, shader->getSourceCode(Shader::PIXEL_STAGE)} }; validateCreation(stages); } -void OslValidator::validateCreation(const std::vector& stages) +void OslValidator::validateCreation(const StageMap& stages) { + // There is only one stage in an OSL shader so only + // the first stage is examined. ShaderValidationErrorList errors; - const std::string errorType("OSL compilation error."); - if (stages.empty() || stages[0].empty()) + const string errorType("OSL compilation error."); + if (stages.empty() || stages.begin()->second.empty()) { errors.push_back("No shader code to validate"); throw ExceptionShaderValidationError(errorType, errors); @@ -295,7 +295,7 @@ void OslValidator::validateCreation(const std::vector& stages) // Dump string to disk. For OSL assume shader is in stage 0 slot. FilePath filePath(_oslOutputFilePathString); filePath = filePath / _oslShaderName; - std::string fileName = filePath.asString(); + string fileName = filePath.asString(); if (fileName.empty()) { fileName = "_osl_temp.osl"; @@ -309,7 +309,7 @@ void OslValidator::validateCreation(const std::vector& stages) // Thus we replace all instances of "object" with "world" to avoid issues. StringMap spaceMap; spaceMap["\"object\""] = "\"world\""; - std::string oslCode = replaceSubstrings(stages[0], spaceMap); + string oslCode = replaceSubstrings(stages.begin()->second, spaceMap); std::ofstream file; file.open(fileName); @@ -323,7 +323,7 @@ void OslValidator::validateCreation(const std::vector& stages) void OslValidator::validateInputs() { ShaderValidationErrorList errors; - const std::string errorType("OSL validation error."); + const string errorType("OSL validation error."); errors.push_back("OSL input validation is not supported at this time."); throw ExceptionShaderValidationError(errorType, errors); @@ -332,7 +332,7 @@ void OslValidator::validateInputs() void OslValidator::validateRender(bool /*orthographicView*/) { ShaderValidationErrorList errors; - const std::string errorType("OSL rendering error."); + const string errorType("OSL rendering error."); if (_oslOutputFilePathString.empty()) { @@ -363,7 +363,7 @@ void OslValidator::validateRender(bool /*orthographicView*/) } } -void OslValidator::save(const std::string& /*fileName*/, bool /*floatingPoint*/) +void OslValidator::save(const string& /*fileName*/, bool /*floatingPoint*/) { // No-op: image save is done as part of rendering. } diff --git a/source/MaterialXRender/ShaderValidators/Osl/OslValidator.h b/source/MaterialXRender/ShaderValidators/Osl/OslValidator.h index 68f1408e47..7eb91189d5 100644 --- a/source/MaterialXRender/ShaderValidators/Osl/OslValidator.h +++ b/source/MaterialXRender/ShaderValidators/Osl/OslValidator.h @@ -33,7 +33,7 @@ class OslValidator : public ShaderValidator virtual ~OslValidator(); /// Color closure OSL string - static std::string OSL_CLOSURE_COLOR_STRING; + static string OSL_CLOSURE_COLOR_STRING; /// @name Setup /// @{ @@ -60,10 +60,9 @@ class OslValidator : public ShaderValidator /// @param shader Input shader void validateCreation(const ShaderPtr shader) override; - /// Validate creation of an OSL program based upon a shader string for a given - /// shader "stage". There is only one shader stage for OSL thus - /// @param stages List of shader strings. Only first string in list is examined. - void validateCreation(const std::vector& stages) override; + /// Validate creation of an OSL program based on shader stage source code. + /// @param stages Map of name and source code for the shader stages. + void validateCreation(const StageMap& stages) override; /// Validate inputs for the compiled OSL program. /// Note: Currently no validation has been implemented. @@ -89,7 +88,7 @@ class OslValidator : public ShaderValidator /// execution. /// @param fileName Name of file to save rendered image to. /// @param floatingPoint Format of output image is floating point. - void save(const std::string& fileName, bool floatingPoint) override; + void save(const string& fileName, bool floatingPoint) override; /// @} /// @name Compilation settings @@ -98,7 +97,7 @@ class OslValidator : public ShaderValidator /// Set the OSL executable path string. Note that it is assumed that this /// references the location of the oslc executable. /// @param executable Full path to OSL compiler executable - void setOslCompilerExecutable(const std::string& executable) + void setOslCompilerExecutable(const string& executable) { _oslCompilerExecutable = executable; } @@ -106,7 +105,7 @@ class OslValidator : public ShaderValidator /// Set the OSL include path string. /// @param includePathString Include path(s) for the OSL compiler. This should include the /// path to stdosl.h - void setOslIncludePath(const std::string& includePathString) + void setOslIncludePath(const string& includePathString) { _oslIncludePathString = includePathString; } @@ -115,7 +114,7 @@ class OslValidator : public ShaderValidator /// During compiler checking an OSL file of the given output name will be used if it /// is not empty. If temp then OSL will be written to a temporary file. /// @param filePathString Full path name - void setOslOutputFilePath(const std::string& filePathString) + void setOslOutputFilePath(const string& filePathString) { _oslOutputFilePathString = filePathString; } @@ -126,7 +125,7 @@ class OslValidator : public ShaderValidator /// input scene file. /// @param outputName Name of shader output /// @param outputName The MaterialX type of the output - void setOslShaderOutput(const std::string& outputName, const std::string& outputType) + void setOslShaderOutput(const string& outputName, const string& outputType) { _oslShaderOutputName = outputName; _oslShaderOutputType = outputType; @@ -135,7 +134,7 @@ class OslValidator : public ShaderValidator /// Set the OSL shading tester path string. Note that it is assumed that this /// references the location of the "testshade" executable. /// @param executable Full path to OSL "testshade" executable - void setOslTestShadeExecutable(const std::string& executable) + void setOslTestShadeExecutable(const string& executable) { _oslTestShadeExecutable = executable; } @@ -143,7 +142,7 @@ class OslValidator : public ShaderValidator /// Set the OSL rendering tester path string. Note that it is assumed that this /// references the location of the "testrender" executable. /// @param executable Full path to OSL "testrender" executable - void setOslTestRenderExecutable(const std::string& executable) + void setOslTestRenderExecutable(const string& executable) { _oslTestRenderExecutable = executable; } @@ -153,7 +152,7 @@ class OslValidator : public ShaderValidator /// - %shader% : which will be replaced with the name of the shader to use /// - %shader_output% : which will be replace with the name of the shader output to use /// @param templateFileName Scene file name - void setOslTestRenderSceneTemplateFile(const std::string& templateFileName) + void setOslTestRenderSceneTemplateFile(const string& templateFileName) { _oslTestRenderSceneTemplateFile = templateFileName; } @@ -161,7 +160,7 @@ class OslValidator : public ShaderValidator /// Set the name of the shader to be used for the input XML scene file. /// The value is used to replace the %shader% token in the file. /// @param shaderName Name of shader - void setOslShaderName(const std::string& shaderName) + void setOslShaderName(const string& shaderName) { _oslShaderName = shaderName; } @@ -169,7 +168,7 @@ class OslValidator : public ShaderValidator /// Set the search path for dependent shaders (.oso files) which are used /// when rendering with testrender. /// @param osoPath Path to .oso files. - void setOslUtilityOSOPath(const std::string& osoPath) + void setOslUtilityOSOPath(const string& osoPath) { _oslUtilityOSOPath = osoPath; } @@ -185,7 +184,7 @@ class OslValidator : public ShaderValidator /// /// Compile OSL code stored in a file. Will throw an exception if an error occurs. /// @param oslFileName Name of OSL file - void compileOSL(const std::string& oslFileName); + void compileOSL(const string& oslFileName); /// @} @@ -195,40 +194,40 @@ class OslValidator : public ShaderValidator /// @param outputPath Path to input .oso file. /// @param shaderName Name of OSL shader. A corresponding .oso file is assumed to exist in the output path folder. /// @param outputName Name of OSL shader output to use. - void shadeOSL(const std::string& outputPath, const std::string& shaderName, const std::string& outputName); + void shadeOSL(const string& outputPath, const string& shaderName, const string& outputName); /// /// Render using OSO input file. Will throw an exception if an error occurs. /// @param outputPath Path to input .oso file. /// @param shaderName Name of OSL shader. A corresponding .oso file is assumed to exist in the output path folder. /// @param outputName Name of OSL shader output to use. - void renderOSL(const std::string& outputPath, const std::string& shaderName, const std::string& outputName); + void renderOSL(const string& outputPath, const string& shaderName, const string& outputName); /// Constructor OslValidator(); private: /// "oslc" executable name` - std::string _oslCompilerExecutable; + string _oslCompilerExecutable; /// OSL include path name - std::string _oslIncludePathString; + string _oslIncludePathString; /// Output file path. File name does not include an extension - std::string _oslOutputFilePathString; + string _oslOutputFilePathString; /// "testshade" executable name - std::string _oslTestShadeExecutable; + string _oslTestShadeExecutable; /// "testrender" executable name - std::string _oslTestRenderExecutable; + string _oslTestRenderExecutable; /// Template scene XML file used for "testrender" - std::string _oslTestRenderSceneTemplateFile; + string _oslTestRenderSceneTemplateFile; /// Name of shader. Used for rendering with "testrender" - std::string _oslShaderName; + string _oslShaderName; /// Name of output on the shader. Used for rendering with "testshade" and "testrender" - std::string _oslShaderOutputName; + string _oslShaderOutputName; /// MaterialX type of the output on the shader. Used for rendering with "testshade" and "testrender" - std::string _oslShaderOutputType; + string _oslShaderOutputType; /// Path for utility shaders (.oso) used when rendering with "testrender" - std::string _oslUtilityOSOPath; + string _oslUtilityOSOPath; /// Use "testshade" or "testender" for render validation bool _useTestRender; }; diff --git a/source/MaterialXRender/ShaderValidators/ShaderValidator.h b/source/MaterialXRender/ShaderValidators/ShaderValidator.h index 68296f909a..991ad992a6 100644 --- a/source/MaterialXRender/ShaderValidators/ShaderValidator.h +++ b/source/MaterialXRender/ShaderValidators/ShaderValidator.h @@ -2,11 +2,11 @@ #define MATERIALX_SHADERVALIDATOR_H #include -#include #include #include #include #include +#include #include #include @@ -20,6 +20,10 @@ using ShaderValidatorPtr = std::shared_ptr; /// class ShaderValidator { + public: + /// A map with name and source code for each shader stage. + using StageMap = std::unordered_map; + public: /// Destructor virtual ~ShaderValidator() {}; @@ -88,9 +92,9 @@ class ShaderValidator /// @param shader Input Shader virtual void validateCreation(const ShaderPtr shader) = 0; - /// Validate creation of program based input shader stage strings - /// @param shader Input stages List of stage string - virtual void validateCreation(const std::vector& stages) = 0; + /// Validate creation of program based on shader stage source code. + /// @param stages Map of name and source code for the shader stages. + virtual void validateCreation(const StageMap& stages) = 0; /// Validate inputs for the program virtual void validateInputs() = 0; diff --git a/source/MaterialXRender/Window/SimpleWindow_linux.cpp b/source/MaterialXRender/Window/SimpleWindow_linux.cpp index 7d0d13e868..99078e4705 100644 --- a/source/MaterialXRender/Window/SimpleWindow_linux.cpp +++ b/source/MaterialXRender/Window/SimpleWindow_linux.cpp @@ -32,9 +32,9 @@ bool SimpleWindow::initialize(char* title, XtAppContext appContext; Widget shell; static Widget batchShell; - static bool initializedXServer = false; if (!applicationShell) { + static bool initializedXServer = false; // Connect to the X Server if (!initializedXServer) { diff --git a/source/MaterialXTest/Geom.cpp b/source/MaterialXTest/Geom.cpp index 9a573e4389..0de685f3ed 100644 --- a/source/MaterialXTest/Geom.cpp +++ b/source/MaterialXTest/Geom.cpp @@ -50,8 +50,15 @@ TEST_CASE("Geom elements", "[geom]") resolver1->setUdimString("1001"); mx::StringResolverPtr resolver2 = image->createStringResolver("/robot2"); resolver2->setUdimString("1002"); - REQUIRE(fileParam->getResolvedValueString(resolver1) == "folder/robot01_diffuse_1001.tif"); - REQUIRE(fileParam->getResolvedValueString(resolver2) == "folder/robot02_diffuse_1002.tif"); + REQUIRE(fileParam->getResolvedValue(resolver1)->asA() == "folder/robot01_diffuse_1001.tif"); + REQUIRE(fileParam->getResolvedValue(resolver2)->asA() == "folder/robot02_diffuse_1002.tif"); + + // Create a geominfo with an attribute. + mx::GeomInfoPtr geominfo4 = doc->addGeomInfo("geominfo4", "/robot1"); + mx::StringVec udimSet = {{"1001", "1002", "1003", "1004"}}; + geominfo4->setGeomAttrValue("udimset", udimSet); + REQUIRE(doc->getGeomAttrValue("udimset", "/robot1")->asA() == udimSet); + REQUIRE(doc->getGeomAttrValue("udimset", "/robot2") == nullptr); // Create a base collection. mx::CollectionPtr collection1 = doc->addCollection("collection1"); @@ -82,18 +89,17 @@ TEST_CASE("GeomPropDef", "[geom]") { mx::DocumentPtr doc = mx::createDocument(); - // Create a geomprop definition for world space normal - mx::GeomPropDefPtr Nworld = doc->addGeomPropDef("Nworld", "normal"); - Nworld->setSpace("world"); + // Declare a GeomPropDef for world-space normal. + mx::GeomPropDefPtr worldNormal = doc->addGeomPropDef("Nworld", "normal"); + worldNormal->setSpace("world"); - // Create a new nodedef and set an input to use the world space normal - // as default geometric data. + // Create a NodeDef with an input that defaults to the declared world-space + // normal property. mx::NodeDefPtr nodeDef = doc->addNodeDef("ND_foo", "color3", "foo"); - mx::InputPtr in1 = doc->addInput("in1", "vector3"); - in1->setDefaultGeomPropString(Nworld->getName()); + mx::InputPtr input = doc->addInput("input1", "vector3"); + input->setDefaultGeomPropString(worldNormal->getName()); - // Test accessing the geomprop - mx::GeomPropDefPtr geomProp = in1->getDefaultGeomProp(); - REQUIRE(geomProp->getName() == "Nworld"); - REQUIRE(geomProp->getSpace() == "world"); + // Validate connections. + REQUIRE(input->getDefaultGeomProp() == worldNormal); + REQUIRE(doc->validate()); } diff --git a/source/MaterialXTest/README.md b/source/MaterialXTest/README.md new file mode 100644 index 0000000000..e1f425baf4 --- /dev/null +++ b/source/MaterialXTest/README.md @@ -0,0 +1,45 @@ +# Unit tests + +## Core Tests + +The names of the files reflect the Element type being tested for the following: + +- Document.cpp +- Element.cpp +- Geom.cpp +- Look.cpp +- Material.cpp +- Node.cpp +- Types.cpp +- Value.cpp + +## Document utilities +- Observer.cpp : Document observation. +- Traversal.cpp : Document traversal. +- Util.cpp : Basic utilities. + +## I/O Tests + +- File.cpp : Basic file path tests. +- XmlIo.cpp : XML document I/O tests. + +## Shader Generation Tests + +- ShaderGen.cpp + +Core shader generation framework tests. + +- ShaderValid.cpp + +Tests for all GLSL and OSL implementations. If rendering tests are enabled via the build options then each Element tested will be rendered if the appropriate backend support is available. +- GLSL: + - Will execute on a Windows machine which supports OpenGL 4.0 or above. +- OSL: Uses the utilities "oslc" and "testrender" utilities from the + [OSL distribution](https://github.com/imageworks/OpenShadingLanguage). + - The test suite has been tested with version 1.9.10. + - The following build options are required to be set: + - MATERIALX_OSLC_EXECUTABLE: Full path to the `oslc` binary. + - MATERIALX_TESTRENDER_EXECUTABLE: Full path to the `testrender` binary. + - MATERIALX_OSL_INCLUDE_PATH: Full path to OSL include paths (i.e. location of `stdosl.h`). + +Refer to the [test suite documentation](../../documents/TestSuite) for more information about the organization of the test suite data used for this test. diff --git a/source/MaterialXTest/ShaderGen.cpp b/source/MaterialXTest/ShaderGen.cpp index 64f37b94c8..4cf844ec0d 100644 --- a/source/MaterialXTest/ShaderGen.cpp +++ b/source/MaterialXTest/ShaderGen.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #ifdef MATERIALX_BUILD_GEN_GLSL #include @@ -37,7 +36,7 @@ namespace mx = MaterialX; -static const std::string LIGHT_SHADER = "lightshader"; +static const std::string LIGHT_SHADER_TYPE = "lightshader"; static const std::string DIRECTIONAL_LIGHT = "directionallight"; static const std::string POINT_LIGHT = "pointlight"; static const std::string SPOT_LIGHT = "spotlight"; @@ -122,40 +121,31 @@ bool getShaderSource(mx::ShaderGeneratorPtr generator, return false; } -// Light type id's for common light shaders -// Using id's matching the OgsFx light sources -// here which simplifies light binding for OGS. -// Note that another target systems could use other ids -// as required by that system. -enum LightType -{ - SPOT = 2, - POINT = 3, - DIRECTIONAL = 4, -}; - -void createLights(mx::DocumentPtr doc, mx::HwLightHandler& lightHandler) +static void registerLightType(mx::DocumentPtr doc, mx::HwShaderGenerator& shadergen, const mx::GenOptions& options) { + // Scan for lights + std::vector lights; for (mx::NodePtr node : doc->getNodes()) { - if (node->getType() == LIGHT_SHADER) + if (node->getType() == LIGHT_SHADER_TYPE) { - lightHandler.addLightSource(node); + lights.push_back(node); + } + } + if (!lights.empty()) + { + // Create a list of unique nodedefs and ids for them + std::unordered_map identifiers; + mx::mapNodeDefToIdentiers(lights, identifiers); + for (auto id : identifiers) + { + mx::NodeDefPtr nodeDef = doc->getNodeDef(id.first); + if (nodeDef) + { + shadergen.bindLightShader(*nodeDef, id.second, options); + } } } -} - -void createLightRig(mx::DocumentPtr doc, mx::HwLightHandler& lightHandler, mx::HwShaderGenerator& shadergen, const mx::GenOptions& options) -{ - // Create the light rig - createLights(doc, lightHandler); - - // Let the shader generator know of these light shaders - lightHandler.bindLightShaders(shadergen, options); - - // Set up IBL inputs - lightHandler.setLightEnvIrradiancePath("documents/TestSuite/Images/san_giuseppe_bridge.hdr"); - lightHandler.setLightEnvRadiancePath("documents/TestSuite/Images/san_giuseppe_bridge_diffuse.hdr"); } static std::string RESULT_DIRECTORY("results/"); @@ -860,7 +850,7 @@ TEST_CASE("Swizzling", "[shadergen]") // Utility to call validate OSL. // For now only call into oslc to compile an OSL file and get the results. // -static void validateOSL(const std::string oslFileName, std::string& errorResult) +static void validateOSL(const std::string& oslFileName, std::string& errorResult) { errorResult.clear(); @@ -907,8 +897,6 @@ TEST_CASE("Shader Interface", "[shadergen]") mx::FilePath searchPath = mx::FilePath::getCurrentPath() / mx::FilePath("documents/Libraries"); loadLibraries({ "stdlib" }, searchPath, doc); - const std::string exampleName = "shader_interface"; - // Create a nodedef taking three color3 and producing another color3 mx::NodeDefPtr nodeDef = doc->addNodeDef("ND_foo", "color3", "foo"); mx::InputPtr fooInputA = nodeDef->addInput("a", "color3"); @@ -939,6 +927,7 @@ TEST_CASE("Shader Interface", "[shadergen]") mx::GenOptions options; #ifdef MATERIALX_BUILD_GEN_OSL + const std::string exampleName = "shader_interface"; { mx::ShaderGeneratorPtr shadergen = mx::ArnoldShaderGenerator::create(); // Add path to find all source code snippets @@ -1475,8 +1464,9 @@ TEST_CASE("Noise", "[shadergen]") const size_t numNoiseType = noiseNodes.size(); for (size_t noiseType = 0; noiseType < numNoiseType; ++noiseType) { +#if defined(MATERIALX_BUILD_GEN_OSL) || defined(MATERIALX_BUILD_GEN_OGSFX) || defined(MATERIALX_BUILD_GEN_GLSL) const std::string shaderName = "test_" + noiseNodes[noiseType]->getName(); - +#endif // Select the noise type switch1->setParameterValue("which", float(noiseType)); @@ -1680,8 +1670,9 @@ TEST_CASE("Subgraphs", "[shadergen]") mx::FilePath examplesSearchPath = mx::FilePath::getCurrentPath() / mx::FilePath("documents/Examples"); loadExamples({ "SubGraphs.mtlx"}, examplesSearchPath, searchPath, doc); +#if defined(MATERIALX_BUILD_GEN_OSL) || defined(MATERIALX_BUILD_GEN_OGSFX) || defined(MATERIALX_BUILD_GEN_GLSL) std::vector exampleGraphNames = { "subgraph_ex1" , "subgraph_ex2" }; - +#endif mx::GenOptions options; #ifdef MATERIALX_BUILD_GEN_OSL @@ -1726,9 +1717,8 @@ TEST_CASE("Subgraphs", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); - + registerLightType(doc, static_cast(*shaderGenerator), options); + for (const std::string& graphName : exampleGraphNames) { mx::NodeGraphPtr nodeGraph = doc->getNodeGraph(graphName); @@ -1756,8 +1746,7 @@ TEST_CASE("Subgraphs", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); for (const std::string& graphName : exampleGraphNames) { @@ -1800,9 +1789,6 @@ TEST_CASE("Materials", "[shadergen]") mx::FilePath materialsFile = mx::FilePath::getCurrentPath() / mx::FilePath("documents/TestSuite/pbrlib/materials/surfaceshader.mtlx"); mx::readFromXmlFile(doc, materialsFile.asString()); - // Get all materials - std::vector materials = doc->getMaterials(); - mx::GenOptions options; #ifdef MATERIALX_BUILD_GEN_OSL @@ -1844,10 +1830,9 @@ TEST_CASE("Materials", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); - for (const mx::MaterialPtr& material : materials) + for (const mx::MaterialPtr& material : doc->getMaterials()) { for (mx::ShaderRefPtr shaderRef : material->getShaderRefs()) { @@ -1872,10 +1857,9 @@ TEST_CASE("Materials", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); - for (const mx::MaterialPtr& material : materials) + for (const mx::MaterialPtr& material : doc->getMaterials()) { for (mx::ShaderRefPtr shaderRef : material->getShaderRefs()) { @@ -2084,7 +2068,9 @@ TEST_CASE("BSDF Layering", "[shadergen]") std::vector elements = { output, shaderRef }; for (mx::ElementPtr elem : elements) { +#if defined(MATERIALX_BUILD_GEN_OSL) || defined(MATERIALX_BUILD_GEN_OGSFX) || defined(MATERIALX_BUILD_GEN_GLSL) const std::string shaderName = exampleName + "_" + elem->getName(); +#endif #ifdef MATERIALX_BUILD_GEN_OSL { @@ -2119,8 +2105,7 @@ TEST_CASE("BSDF Layering", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); mx::ShaderPtr shader = shaderGenerator->generate(shaderName, elem, options); REQUIRE(shader != nullptr); @@ -2140,8 +2125,7 @@ TEST_CASE("BSDF Layering", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); mx::ShaderPtr shader = shaderGenerator->generate(shaderName, elem, options); REQUIRE(shader != nullptr); @@ -2277,8 +2261,7 @@ TEST_CASE("Transparency", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); // Track if this shader needs to handle transparency options.hwTransparency = isTransparentSurface(shaderRef, *shaderGenerator); @@ -2306,8 +2289,7 @@ TEST_CASE("Transparency", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); // Track if this shader needs to handle transparency options.hwTransparency = isTransparentSurface(shaderRef, *shaderGenerator); @@ -2410,8 +2392,7 @@ TEST_CASE("Surface Layering", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); // Specify if this shader needs to handle transparency options.hwTransparency = isTransparentSurface(shaderRef, *shaderGenerator); @@ -2434,8 +2415,7 @@ TEST_CASE("Surface Layering", "[shadergen]") shaderGenerator->registerSourceCodeSearchPath(searchPath); // Setup lighting - mx::HwLightHandlerPtr lightHandler = mx::HwLightHandler::create(); - createLightRig(doc, *lightHandler, static_cast(*shaderGenerator), options); + registerLightType(doc, static_cast(*shaderGenerator), options); // Specify if this shader needs to handle transparency options.hwTransparency = isTransparentSurface(shaderRef, *shaderGenerator); diff --git a/source/MaterialXTest/ShaderValid.cpp b/source/MaterialXTest/ShaderValid.cpp index 45cb2e784f..ad53c9d8ca 100644 --- a/source/MaterialXTest/ShaderValid.cpp +++ b/source/MaterialXTest/ShaderValid.cpp @@ -14,8 +14,8 @@ #include #include #include -#include #include +#include #ifdef MATERIALX_BUILD_GEN_GLSL #include @@ -50,7 +50,48 @@ namespace mx = MaterialX; extern void loadLibrary(const mx::FilePath& file, mx::DocumentPtr doc); extern void loadLibraries(const mx::StringVec& libraryNames, const mx::FilePath& searchPath, mx::DocumentPtr doc, const std::set* excludeFiles = nullptr); -extern void createLightRig(mx::DocumentPtr doc, mx::HwLightHandler& lightHandler, mx::HwShaderGenerator& shadergen, const mx::GenOptions& options); + +void createLightRig(mx::DocumentPtr doc, mx::HwLightHandler& lightHandler, mx::HwShaderGenerator& shadergen, const mx::GenOptions& options, + const mx::FilePath& envIrradiancePath, const mx::FilePath& envRadiancePath) +{ + // Scan for lights + const std::string LIGHT_SHADER_TYPE("lightshader"); + std::vector lights; + for (mx::NodePtr node : doc->getNodes()) + { + if (node->getType() == LIGHT_SHADER_TYPE) + { + lights.push_back(node); + } + } + if (!lights.empty()) + { + // Set the list of lights on the with the generator + lightHandler.setLightSources(lights); + + // Find light types (node definitions) and generate ids. + // Register types and ids with the generator + std::unordered_map identifiers; + mx::mapNodeDefToIdentiers(lights, identifiers); + for (auto id : identifiers) + { + mx::NodeDefPtr nodeDef = doc->getNodeDef(id.first); + if (nodeDef) + { + shadergen.bindLightShader(*nodeDef, id.second, options); + } + } + } + + // Clamp the number of light sources to the number found + unsigned int lightSourceCount = static_cast(lightHandler.getLightSources().size()); + shadergen.setMaxActiveLightSources(lightSourceCount); + + // Set up IBL inputs + lightHandler.setLightEnvIrradiancePath(envIrradiancePath); + lightHandler.setLightEnvRadiancePath(envRadiancePath); +} + #ifdef MATERIALX_BUILD_GEN_GLSL // @@ -104,22 +145,14 @@ static mx::GlslValidatorPtr createGLSLValidator(const std::string& fileName, std static mx::OslValidatorPtr createOSLValidator(std::ostream& log) { bool initialized = false; - bool initializeTestRender = false; mx::OslValidatorPtr validator = mx::OslValidator::create(); -#ifdef MATERIALX_OSLC_EXECUTABLE - validator->setOslCompilerExecutable(MATERIALX_OSLC_EXECUTABLE); -#endif -#ifdef MATERIALX_TESTSHADE_EXECUTABLE + const std::string oslcExecutable(MATERIALX_OSLC_EXECUTABLE); + validator->setOslCompilerExecutable(oslcExecutable); validator->setOslTestShadeExecutable(MATERIALX_TESTSHADE_EXECUTABLE); -#endif -#ifdef MATERIALX_TESTRENDER_EXECUTABLE - validator->setOslTestRenderExecutable(MATERIALX_TESTRENDER_EXECUTABLE); - initializeTestRender = true; -#endif -#ifdef MATERIALX_OSL_INCLUDE_PATH + const std::string testRenderExecutable(MATERIALX_TESTRENDER_EXECUTABLE); + validator->setOslTestRenderExecutable(testRenderExecutable); validator->setOslIncludePath(MATERIALX_OSL_INCLUDE_PATH); -#endif try { validator->initialize(); @@ -128,7 +161,7 @@ static mx::OslValidatorPtr createOSLValidator(std::ostream& log) initialized = true; // Pre-compile some required shaders for testrender - if (initializeTestRender) + if (!oslcExecutable.empty() && !testRenderExecutable.empty()) { mx::FilePath shaderPath = mx::FilePath::getCurrentPath() / mx::FilePath("documents/TestSuite/Utilities/"); validator->setOslOutputFilePath(shaderPath); @@ -146,7 +179,7 @@ static mx::OslValidatorPtr createOSLValidator(std::ostream& log) validator->setOslUtilityOSOPath(shaderPath); } } - catch(mx::ExceptionShaderValidationError e) + catch(mx::ExceptionShaderValidationError& e) { for (auto error : e.errorLog()) { @@ -199,6 +232,8 @@ class ShaderValidTestOptions output << "\tDump GLSL Uniforms and Attributes " << dumpGlslUniformsAndAttributes << std::endl; output << "\tGLSL Non-Shader Geometry: " << glslNonShaderGeometry.asString() << std::endl; output << "\tGLSL Shader Geometry: " << glslShaderGeometry.asString() << std::endl; + output << "\tRadiance IBL File Path " << radianceIBLPath.asString() << std::endl; + output << "\tIrradiance IBL File Path: " << irradianceIBLPath.asString() << std::endl; } // Filter list of files to only run validation on. @@ -251,6 +286,12 @@ class ShaderValidTestOptions // Shader GLSL geometry file MaterialX::FilePath glslShaderGeometry = "shaderball.obj"; + + // Radiance IBL file + MaterialX::FilePath radianceIBLPath; + + // IradianceIBL file + MaterialX::FilePath irradianceIBLPath; }; // Per language profile times @@ -365,7 +406,6 @@ void getGenerationOptions(const ShaderValidTestOptions& testOptions, std::vector { mx::GenOptions reducedOption; reducedOption.shaderInterfaceType = mx::SHADER_INTERFACE_REDUCED; - reducedOption.validate = testOptions.validateElementToRender; optionsList.push_back(reducedOption); } // Alway fallback to complete if no options specified. @@ -373,7 +413,6 @@ void getGenerationOptions(const ShaderValidTestOptions& testOptions, std::vector { mx::GenOptions completeOption; completeOption.shaderInterfaceType = mx::SHADER_INTERFACE_COMPLETE; - completeOption.validate = testOptions.validateElementToRender; optionsList.push_back(completeOption); } } @@ -385,6 +424,17 @@ static void runOGSFXValidation(const std::string& shaderName, mx::TypedElementPt { AdditiveScopedTimer totalOgsFXTime(profileTimes.ogsfxTimes.totalTime, "OGSFX total time"); + // Perform validation if requested + if (testOptions.validateElementToRender) + { + std::string message; + if (!element->validate(&message)) + { + log << "Element is invalid: " << message << std::endl; + return; + } + } + std::vector optionsList; getGenerationOptions(testOptions, optionsList); @@ -478,6 +528,17 @@ static void runGLSLValidation(const std::string& shaderName, mx::TypedElementPtr { AdditiveScopedTimer totalGLSLTime(profileTimes.glslTimes.totalTime, "GLSL total time"); + // Perform validation if requested + if (testOptions.validateElementToRender) + { + std::string message; + if (!element->validate(&message)) + { + log << "Element is invalid: " << message << std::endl; + return; + } + } + std::vector optionsList; getGenerationOptions(testOptions, optionsList); @@ -719,6 +780,17 @@ static void runOSLValidation(const std::string& shaderName, mx::TypedElementPtr { AdditiveScopedTimer totalOSLTime(profileTimes.oslTimes.totalTime, "OSL total time"); + // Perform validation if requested + if (testOptions.validateElementToRender) + { + std::string message; + if (!element->validate(&message)) + { + log << "Element is invalid: " << message << std::endl; + return; + } + } + std::vector optionsList; getGenerationOptions(testOptions, optionsList); @@ -871,6 +943,8 @@ bool getTestOptions(const std::string& optionFile, ShaderValidTestOptions& optio const std::string DUMP_GENERATED_CODE_STRING("dumpGeneratedCode"); const std::string GLSL_NONSHADER_GEOMETRY_STRING("glslNonShaderGeometry"); const std::string GLSL_SHADER_GEOMETRY_STRING("glslShaderGeometry"); + const std::string RADIANCE_IBL_PATH_STRING("radianceIBLPath"); + const std::string IRRADIANCE_IBL_PATH_STRING("irradianceIBLPath"); options.overrideFiles.clear(); options.dumpGeneratedCode = false; @@ -957,6 +1031,14 @@ bool getTestOptions(const std::string& optionFile, ShaderValidTestOptions& optio { options.glslShaderGeometry = p->getValueString(); } + else if (name == RADIANCE_IBL_PATH_STRING) + { + options.radianceIBLPath = p->getValueString(); + } + else if (name == IRRADIANCE_IBL_PATH_STRING) + { + options.irradianceIBLPath = p->getValueString(); + } } } } @@ -973,12 +1055,13 @@ bool getTestOptions(const std::string& optionFile, ShaderValidTestOptions& optio options.saveImages = false; } - // If implementation count check is required, then at a minimum OSL and GLSL - // code generation must execute to be able to check implementation usage. + // If implementation count check is required, then OSL and GLSL/OGSFX + // code generation must be executed to be able to check implementation usage. if (options.checkImplCount) { options.runGLSLTests = true; options.runOSLTests = true; + options.runOGSFXTests = true; } return true; } @@ -1037,7 +1120,10 @@ void printRunLog(const ShaderValidProfileTimes &profileTimes, const ShaderValidT { "arrayappend", "backfacing", "screen", "curveadjust", "dot_surfaceshader", "mix_surfaceshader" "displacementShader", "displacementshader", "volumeshader", "IM_dot_filename", "ambientocclusion", "dot_lightshader", - "geomattrvalue_integer", "geomattrvalue_boolean", "geomattrvalue_string" + "geomattrvalue_integer", "geomattrvalue_boolean", "geomattrvalue_string", "constant_matrix33", "add_matrix33FA", + "add_matrix33", "subtract_matrix33FA", "subtract_matrix33", "multiply_matrix33", "divide_matrix33", "invert_matrix33", + "transpose_matrix33", "transformvector_vector3M", "transformnormal_vector3M", "transformpoint_vector3M", + "determinant_matrix33", "IM_dot_", "IM_constant_string_", "IM_constant_filename_" }; const std::string OSL_STRING("osl"); const std::string GEN_OSL_STRING(mx::OslShaderGenerator::LANGUAGE); @@ -1115,25 +1201,15 @@ void printRunLog(const ShaderValidProfileTimes &profileTimes, const ShaderValidT } size_t libraryCount = libraryImpls.size(); profilingLog << "Tested: " << implementationUseCount << " out of: " << libraryCount << " library implementations." << std::endl; - // TODO: Add a CHECK when all implementations have been tested using unit tests. - // CHECK(implementationUseCount == libraryCount); + CHECK(implementationUseCount == libraryCount); } } TEST_CASE("MaterialX documents", "[shadervalid]") { - bool runValidation = false; -#ifdef MATERIALX_BUILD_GEN_GLSL - runValidation = true; -#endif -#ifdef MATERIALX_BUILD_GEN_OSL - runValidation = true; +#if !defined(MATERIALX_BUILD_GEN_GLSL) && !defined(MATERIALX_BUILD_GEN_OSL) && defined(MATERIALX_BUILD_GEN_OGSFX) + return; #endif - if (!runValidation) - { - // No generators exist so there is nothing to test. Just return - return; - } // Profiling times ShaderValidProfileTimes profileTimes; @@ -1206,7 +1282,6 @@ TEST_CASE("MaterialX documents", "[shadervalid]") mx::FilePath searchPath = mx::FilePath::getCurrentPath() / mx::FilePath("documents/Libraries"); // Create validators and generators - const bool orthographicView = false; #if defined(MATERIALX_BUILD_GEN_GLSL) || defined(MATERIALX_BUILD_GEN_OGSFX) mx::DefaultColorManagementSystemPtr glslColorManagementSystem = nullptr; mx::GlslValidatorPtr glslValidator = nullptr; @@ -1300,11 +1375,8 @@ TEST_CASE("MaterialX documents", "[shadervalid]") // Add lights as a dependency mx::GenOptions genOptions; glslLightHandler = mx::HwLightHandler::create(); - createLightRig(dependLib, *glslLightHandler, *glslShaderGenerator, genOptions); - - // Clamp the number of light sources to the number bound - size_t lightSourceCount = glslLightHandler->getLightSources().size(); - glslShaderGenerator->setMaxActiveLightSources(lightSourceCount); + createLightRig(dependLib, *glslLightHandler, *glslShaderGenerator, genOptions, + options.radianceIBLPath, options.irradianceIBLPath); } } #endif @@ -1320,11 +1392,8 @@ TEST_CASE("MaterialX documents", "[shadervalid]") // Add lights as a dependency mx::GenOptions genOptions; ogsfxLightHandler = mx::HwLightHandler::create(); - createLightRig(dependLib, *ogsfxLightHandler, *ogsfxShaderGenerator, genOptions); - - // Clamp the number of light sources to the number bound - size_t lightSourceCount = ogsfxLightHandler->getLightSources().size(); - ogsfxShaderGenerator->setMaxActiveLightSources(lightSourceCount); + createLightRig(dependLib, *ogsfxLightHandler, *ogsfxShaderGenerator, genOptions, + options.radianceIBLPath, options.irradianceIBLPath); } } #endif diff --git a/source/MaterialXTest/XmlIo.cpp b/source/MaterialXTest/XmlIo.cpp index fede23ccb5..6e84bc85d1 100644 --- a/source/MaterialXTest/XmlIo.cpp +++ b/source/MaterialXTest/XmlIo.cpp @@ -5,6 +5,7 @@ #include +#include #include namespace mx = MaterialX; @@ -31,7 +32,7 @@ TEST_CASE("Load content", "[xmlio]") "BxDF/Disney_BRDF_2012.mtlx", "BxDF/Disney_BSDF_2015.mtlx", }; - std::string searchPath = "documents/Libraries/stdlib;documents/Examples"; + std::string searchPath = "documents/Libraries/stdlib;documents/Libraries/stdlib/osl;documents/Examples"; // Read the standard library. std::vector libs; @@ -132,15 +133,16 @@ TEST_CASE("Load content", "[xmlio]") } mx::TypedElementPtr typedElem = elem->asA(); - mx::NodePtr node = elem->asA(); if (typedElem && typedElem->hasType() && !typedElem->isMultiOutputType()) { if (!typedElem->getTypeDef()) { - WARN("[" + node->getActiveSourceUri() + "] TypedElement " + node->getName() + " has no matching TypeDef"); + WARN("[" + typedElem->getActiveSourceUri() + "] TypedElement " + typedElem->getName() + " has no matching TypeDef"); referencesValid = false; } } + + mx::NodePtr node = elem->asA(); if (node) { if (!node->getNodeDef()) diff --git a/source/PyMaterialX/CMakeLists.txt b/source/PyMaterialX/CMakeLists.txt index e4a8f5de97..5c7332d34b 100644 --- a/source/PyMaterialX/CMakeLists.txt +++ b/source/PyMaterialX/CMakeLists.txt @@ -51,6 +51,10 @@ set_target_properties( target_link_libraries( PyMaterialX PUBLIC MaterialXFormat + MaterialXGenShader + MaterialXGenGlsl + MaterialXGenOgsFx + MaterialXGenOsl PRIVATE ${CMAKE_DL_LIBS}) install(TARGETS PyMaterialX diff --git a/source/PyMaterialX/PyColorManagement.cpp b/source/PyMaterialX/PyColorManagement.cpp new file mode 100644 index 0000000000..3377b9a734 --- /dev/null +++ b/source/PyMaterialX/PyColorManagement.cpp @@ -0,0 +1,82 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// +#include + +#include +#include +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +class PyColorManagementSystem : public mx::ColorManagementSystem +{ + public: + PyColorManagementSystem(const std::string& configFile) : + mx::ColorManagementSystem(configFile) + { + } + + const std::string& getName() const override + { + PYBIND11_OVERLOAD_PURE( + const std::string&, + mx::ColorManagementSystem, + getName + ); + } + + protected: + std::string getImplementationName(const mx::ColorSpaceTransform& transform) override + { + PYBIND11_OVERLOAD_PURE( + std::string, + mx::ColorManagementSystem, + getImplementationName, + transform + ); + } +}; + +class PyDefaultColorManagementSystem : public mx::DefaultColorManagementSystem +{ + public: + PyDefaultColorManagementSystem(const std::string& configFile) : + mx::DefaultColorManagementSystem(configFile) + { + } + + const std::string& getName() const override + { + PYBIND11_OVERLOAD( + const std::string&, + mx::DefaultColorManagementSystem, + getName + ); + } +}; + +void bindPyColorManagement(py::module& mod) +{ + py::class_(mod, "ColorSpaceTransform") + .def(py::init([](const std::string& ss, const std::string& ts, const mx::TypeDesc* t) { return new mx::ColorSpaceTransform(ss, ts, t); })) + .def_readwrite("sourceSpace", &mx::ColorSpaceTransform::sourceSpace) + .def_readwrite("targetSpace", &mx::ColorSpaceTransform::targetSpace) + .def_readwrite("type", &mx::ColorSpaceTransform::type); + + py::class_(mod, "ColorManagementSystem") + .def(py::init([](const std::string& configFile) { return new PyColorManagementSystem(configFile); })) + .def("getName", &mx::ColorManagementSystem::getName) + .def("getConfigFile", &mx::ColorManagementSystem::getConfigFile) + .def("setConfigFile", &mx::ColorManagementSystem::setConfigFile) + .def("loadLibrary", &mx::ColorManagementSystem::loadLibrary) + .def("supportsTransform", &mx::ColorManagementSystem::supportsTransform) + .def("createNode", &mx::ColorManagementSystem::createNode); + + py::class_(mod, "DefaultColorManagementSystem") + .def_static("create", &mx::DefaultColorManagementSystem::create) + .def(py::init([](const std::string& configFile) { return new PyDefaultColorManagementSystem(configFile); })) + .def("getName", &mx::DefaultColorManagementSystem::getName); +} diff --git a/source/PyMaterialX/PyDocument.cpp b/source/PyMaterialX/PyDocument.cpp index d5d81dabd7..798e16ed19 100644 --- a/source/PyMaterialX/PyDocument.cpp +++ b/source/PyMaterialX/PyDocument.cpp @@ -35,6 +35,8 @@ void bindPyDocument(py::module& mod) .def("getGeomInfo", &mx::Document::getGeomInfo) .def("getGeomInfos", &mx::Document::getGeomInfos) .def("removeGeomInfo", &mx::Document::removeGeomInfo) + .def("getGeomAttrValue", &mx::Document::getGeomAttrValue, + py::arg("geomAttrName"), py::arg("geom") = mx::UNIVERSAL_GEOM_NAME) .def("addLook", &mx::Document::addLook, py::arg("name") = mx::EMPTY_STRING) .def("getLook", &mx::Document::getLook) diff --git a/source/PyMaterialX/PyFile.cpp b/source/PyMaterialX/PyFile.cpp new file mode 100644 index 0000000000..272aa4b3d4 --- /dev/null +++ b/source/PyMaterialX/PyFile.cpp @@ -0,0 +1,37 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyFile(py::module& mod) +{ + py::enum_(mod, "Type") + .value("TypeRelative", mx::FilePath::Type::TypeRelative) + .value("TypeAbsolute", mx::FilePath::Type::TypeAbsolute) + .value("TypeNetwork", mx::FilePath::Type::TypeNetwork); + + py::enum_(mod, "Format") + .value("FormatWindows", mx::FilePath::Format::FormatWindows) + .value("FormatPosix", mx::FilePath::Format::FormatPosix) + .value("FormatNative", mx::FilePath::Format::FormatNative); + + py::class_(mod, "FilePath") + .def_static("getCurrentPath", &mx::FilePath::getCurrentPath) + .def(py::init<>()) + .def(py::init()) + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::self / py::self) + .def("assign", &mx::FilePath::assign) + .def("asString", &mx::FilePath::asString) + .def("isEmpty", &mx::FilePath::isEmpty) + .def("isAbsolute", &mx::FilePath::isAbsolute) + .def("getBaseName", &mx::FilePath::getBaseName) + .def("exists", &mx::FilePath::exists); +} diff --git a/source/PyMaterialX/PyGenOptions.cpp b/source/PyMaterialX/PyGenOptions.cpp new file mode 100644 index 0000000000..8af4b0e261 --- /dev/null +++ b/source/PyMaterialX/PyGenOptions.cpp @@ -0,0 +1,23 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyGenOptions(py::module& mod) +{ + py::enum_(mod, "ShaderInterfaceType") + .value("SHADER_INTERFACE_COMPLETE", mx::ShaderInterfaceType::SHADER_INTERFACE_COMPLETE) + .value("SHADER_INTERFACE_REDUCED", mx::ShaderInterfaceType::SHADER_INTERFACE_REDUCED); + + py::class_(mod, "GenOptions") + .def_readwrite("shaderInterfaceType", &mx::GenOptions::shaderInterfaceType) + .def_readwrite("hwTransparency", &mx::GenOptions::hwTransparency) + .def_readwrite("targetColorSpaceOverride", &mx::GenOptions::targetColorSpaceOverride) + .def(py::init<>()); +} diff --git a/source/PyMaterialX/PyGenShaderUtil.cpp b/source/PyMaterialX/PyGenShaderUtil.cpp new file mode 100644 index 0000000000..c16ed53597 --- /dev/null +++ b/source/PyMaterialX/PyGenShaderUtil.cpp @@ -0,0 +1,16 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// +#include + +#include +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyGenShaderUtil(py::module& mod) +{ + mod.def("isTransparentSurface", &mx::isTransparentSurface); +} diff --git a/source/PyMaterialX/PyHwShader.cpp b/source/PyMaterialX/PyHwShader.cpp new file mode 100644 index 0000000000..c0efa0279a --- /dev/null +++ b/source/PyMaterialX/PyHwShader.cpp @@ -0,0 +1,19 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// +#include + +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyHwShader(py::module& mod) +{ + py::class_(mod, "HwShader") + .def("getVertexDataBlock", &mx::HwShader::getVertexDataBlock) + .def("hasTransparency", &mx::HwShader::hasTransparency); +} diff --git a/source/PyMaterialX/PyMaterialX.cpp b/source/PyMaterialX/PyMaterialX.cpp index 3556c86941..9ec68544f1 100644 --- a/source/PyMaterialX/PyMaterialX.cpp +++ b/source/PyMaterialX/PyMaterialX.cpp @@ -23,6 +23,13 @@ void bindPyUtil(py::module& mod); void bindPyValue(py::module& mod); void bindPyVariant(py::module& mod); void bindPyXmlIo(py::module& mod); +void bindPyGenOptions(py::module& mod); +void bindPyShaderGenerator(py::module& mod); +void bindPyShader(py::module& mod); +void bindPyHwShader(py::module& mod); +void bindPyColorManagement(py::module& mod); +void bindPyGenShaderUtil(py::module& mod); +void bindPyFile(py::module& mod); PYBIND11_MODULE(PyMaterialX, mod) { @@ -44,4 +51,11 @@ PYBIND11_MODULE(PyMaterialX, mod) bindPyUtil(mod); bindPyException(mod); bindPyXmlIo(mod); + bindPyGenOptions(mod); + bindPyShaderGenerator(mod); + bindPyShader(mod); + bindPyHwShader(mod); + bindPyColorManagement(mod); + bindPyGenShaderUtil(mod); + bindPyFile(mod); } diff --git a/source/PyMaterialX/PyShader.cpp b/source/PyMaterialX/PyShader.cpp new file mode 100644 index 0000000000..5d14453289 --- /dev/null +++ b/source/PyMaterialX/PyShader.cpp @@ -0,0 +1,51 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// +#include + +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +void bindPyShader(py::module& mod) +{ + py::class_(mod, "Variable") + .def(py::init([](const mx::TypeDesc* t, const std::string& n, const std::string& p, const std::string& s, mx::ValuePtr v) { return new mx::Shader::Variable(t, n, p, s, v); })) + .def_readwrite("type", &mx::Shader::Variable::type) + .def_readwrite("name", &mx::Shader::Variable::name) + .def_readwrite("path", &mx::Shader::Variable::path) + .def_readwrite("semantic", &mx::Shader::Variable::semantic) + .def_readwrite("value", &mx::Shader::Variable::value); + + py::class_(mod, "VariableBlock") + .def(py::init([](const std::string& n, const std::string& i) { return new mx::Shader::VariableBlock(n, i); })) + .def("empty", &mx::Shader::VariableBlock::empty) + .def("size", &mx::Shader::VariableBlock::size) + .def("__len__", &mx::Shader::VariableBlock::size) + .def("__getitem__", [](const mx::Shader::VariableBlock &vb, size_t i) + { + if (i >= vb.size()) throw py::index_error(); + return vb[i]; + }, py::return_value_policy::reference_internal) + .def_readwrite("name", &mx::Shader::VariableBlock::name) + .def_readwrite("instance", &mx::Shader::VariableBlock::instance) + .def_readwrite("variableMap", &mx::Shader::VariableBlock::variableMap) + .def_readwrite("variableOrder", &mx::Shader::VariableBlock::variableOrder); + + py::class_(mod, "Shader") + .def("numStages", &mx::Shader::numStages) + .def("getConstantBlock", &mx::Shader::getConstantBlock) + .def("getUniformBlocks", &mx::Shader::getUniformBlocks) + .def("getUniformBlock", &mx::Shader::getUniformBlock) + .def("getAppDataBlock", &mx::Shader::getAppDataBlock) + .def("getOutputBlock", &mx::Shader::getOutputBlock) + .def("getName", &mx::Shader::getName) + .def("getSourceCode", &mx::Shader::getSourceCode) + .def_readonly_static("PIXEL_STAGE", &mx::Shader::PIXEL_STAGE) + .def_readonly_static("PRIVATE_UNIFORMS", &mx::Shader::PRIVATE_UNIFORMS) + .def_readonly_static("PUBLIC_UNIFORMS", &mx::Shader::PUBLIC_UNIFORMS); +} diff --git a/source/PyMaterialX/PyShaderGenerator.cpp b/source/PyMaterialX/PyShaderGenerator.cpp new file mode 100644 index 0000000000..b8b4436c60 --- /dev/null +++ b/source/PyMaterialX/PyShaderGenerator.cpp @@ -0,0 +1,361 @@ +// +// TM & (c) 2019 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd. +// All rights reserved. See LICENSE.txt for license. +// + +#include + +#include +#include +#include +#include +#include + +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +class PyShaderGenerator : public mx::ShaderGenerator +{ + public: + PyShaderGenerator(mx::SyntaxPtr syntax) : + mx::ShaderGenerator(syntax) + { + } + + const std::string& getLanguage() const override + { + PYBIND11_OVERLOAD_PURE( + const std::string&, + mx::ShaderGenerator, + getLanguage + ); + } + + const std::string& getTarget() const override + { + PYBIND11_OVERLOAD_PURE( + const std::string&, + mx::ShaderGenerator, + getTarget + ); + } + + mx::ShaderPtr generate(const std::string& shaderName, mx::ElementPtr element, const mx::GenOptions& options) override + { + PYBIND11_OVERLOAD_PURE( + mx::ShaderPtr, + mx::ShaderGenerator, + generate, + shaderName, + element, + options + ); + } + + void emitTypeDefinitions(mx::Shader& shader) override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitTypeDefinitions, + shader + ); + } + + void emitFunctionDefinitions(mx::Shader& shader) override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitFunctionDefinitions, + shader + ); + } + + void emitFunctionCalls(const mx::GenContext& context, mx::Shader& shader) override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitFunctionCalls, + context, + shader + ); + } + + void emitFinalOutput(mx::Shader& shader) const override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitFinalOutput, + shader + ); + } + + void emitInput(const mx::GenContext& context, const mx::ShaderInput* input, mx::Shader& shader) const override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitInput, + context, + input, + shader + ); + } + + void getInput(const mx::GenContext& context, const mx::ShaderInput* input, std::string& result) const override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + getInput, + context, + input, + result + ); + } + + void emitOutput(const mx::GenContext& context, const mx::ShaderOutput* output, bool includeType, bool assignDefault, mx::Shader& shader) const override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitOutput, + context, + output, + includeType, + assignDefault, + shader + ); + } + + void emitVariableBlock(const mx::Shader::VariableBlock& block, const std::string& qualifier, const std::string& separator, mx::Shader& shader) override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitVariableBlock, + block, + qualifier, + separator, + shader + ); + } + + void emitVariable(const mx::Shader::Variable& variable, const std::string& qualifier, mx::Shader& shader) override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + emitVariable, + variable, + qualifier, + shader + ); + } + + void addContextIDs(mx::ShaderNode* node) const override + { + PYBIND11_OVERLOAD( + void, + mx::ShaderGenerator, + addContextIDs, + node + ); + } + + mx::ValuePtr remapEnumeration(const mx::ValueElementPtr& input, const mx::InterfaceElement& mappingElement, const mx::TypeDesc*& enumerationType) override + { + PYBIND11_OVERLOAD( + mx::ValuePtr, + mx::ShaderGenerator, + remapEnumeration, + input, + mappingElement, + enumerationType + ); + } + + mx::ValuePtr remapEnumeration(const std::string& inputName, const std::string& inputValue, const std::string& inputType, + const mx::InterfaceElement& mappingElement, const mx::TypeDesc*& enumerationType) override + { + PYBIND11_OVERLOAD( + mx::ValuePtr, + mx::ShaderGenerator, + remapEnumeration, + inputName, + inputValue, + inputType, + mappingElement, + enumerationType + ); + } +}; + +class PyGlslShaderGenerator : public mx::GlslShaderGenerator +{ + public: + using GlslShaderGenerator::GlslShaderGenerator; + + mx::ShaderPtr generate(const std::string& shaderName, mx::ElementPtr element, const mx::GenOptions& options) override + { + PYBIND11_OVERLOAD( + mx::ShaderPtr, + mx::GlslShaderGenerator, + generate, + shaderName, + element, + options + ); + } + + const std::string& getLanguage() const override + { + PYBIND11_OVERLOAD( + const std::string&, + mx::GlslShaderGenerator, + getLanguage + ); + } + + const std::string& getTarget() const override + { + PYBIND11_OVERLOAD( + const std::string&, + mx::GlslShaderGenerator, + getTarget + ); + } + + const std::string& getVersion() const override + { + PYBIND11_OVERLOAD( + const std::string&, + mx::GlslShaderGenerator, + getVersion + ); + } +}; + +class PyOgsFxShaderGenerator : public mx::OgsFxShaderGenerator +{ + public: + using OgsFxShaderGenerator::OgsFxShaderGenerator; + + mx::ShaderPtr generate(const std::string& shaderName, mx::ElementPtr element, const mx::GenOptions& options) override + { + PYBIND11_OVERLOAD( + mx::ShaderPtr, + mx::OgsFxShaderGenerator, + generate, + shaderName, + element, + options + ); + } + + const std::string& getTarget() const override + { + PYBIND11_OVERLOAD( + const std::string&, + mx::OgsFxShaderGenerator, + getTarget + ); + } +}; + +class PyOslShaderGenerator : public mx::OslShaderGenerator +{ + public: + PyOslShaderGenerator() : + mx::OslShaderGenerator() + { + } + + mx::ShaderPtr generate(const std::string& shaderName, mx::ElementPtr element, const mx::GenOptions& options) override + { + PYBIND11_OVERLOAD( + mx::ShaderPtr, + mx::OslShaderGenerator, + generate, + shaderName, + element, + options + ); + } + + const std::string& getTarget() const override + { + PYBIND11_OVERLOAD_PURE( + const std::string&, + mx::ShaderGenerator, + getTarget + ); + } + + const std::string& getLanguage() const override + { + PYBIND11_OVERLOAD( + const std::string&, + mx::OslShaderGenerator, + getLanguage + ); + } +}; + +class PyArnoldShaderGenerator : public mx::ArnoldShaderGenerator +{ + public: + using ArnoldShaderGenerator::ArnoldShaderGenerator; + + const std::string& getTarget() const override + { + PYBIND11_OVERLOAD_PURE( + const std::string&, + mx::ArnoldShaderGenerator, + getTarget + ); + } + +}; + + +void bindPyShaderGenerator(py::module& mod) +{ + py::class_(mod, "ShaderGenerator") + .def(py::init([](mx::SyntaxPtr syntax) { return new PyShaderGenerator(syntax); })) + .def("getLanguage", &mx::ShaderGenerator::getLanguage) + .def("getTarget", &mx::ShaderGenerator::getTarget) + .def("generate", &mx::ShaderGenerator::generate) + .def("setColorManagementSystem", &mx::ShaderGenerator::setColorManagementSystem) + .def("getColorManagementSystem", &mx::ShaderGenerator::getColorManagementSystem) + .def("registerSourceCodeSearchPath", &mx::ShaderGenerator::registerSourceCodeSearchPath); + + py::class_(mod, "GlslShaderGenerator") + .def_static("create", &mx::GlslShaderGenerator::create) + .def(py::init([](){ return new PyGlslShaderGenerator(); })) + .def("generate", &mx::GlslShaderGenerator::generate) + .def("getLanguage", &mx::GlslShaderGenerator::getLanguage) + .def("getTarget", &mx::GlslShaderGenerator::getTarget) + .def("getVersion", &mx::GlslShaderGenerator::getVersion); + + py::class_(mod, "OgsFxShaderGenerator") + .def_static("create", &mx::OgsFxShaderGenerator::create) + .def(py::init([](){ return new PyOgsFxShaderGenerator(); })) + .def("generate", &mx::OgsFxShaderGenerator::generate) + .def("getTarget", &mx::OgsFxShaderGenerator::getTarget); + + py::class_(mod, "OslShaderGenerator") + .def(py::init([](){ return new PyOslShaderGenerator(); })) + .def("generate", &mx::OslShaderGenerator::generate) + .def("getLanguage", &mx::OslShaderGenerator::getLanguage); + + py::class_(mod, "ArnoldShaderGenerator") + .def_static("create", &mx::ArnoldShaderGenerator::create) + .def(py::init([](){ return new PyArnoldShaderGenerator(); })) + .def("getTarget", &mx::ArnoldShaderGenerator::getTarget); +} diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000000..2d9559ccd1 --- /dev/null +++ b/source/README.md @@ -0,0 +1,77 @@ +# Core Specification Support + +- [MaterialXCore](MaterialXCore): Support for the core MaterialX elements +and graph traversal. +- [MaterialXFormat](MaterialXFormat): XML serialization support. +- [PyMaterialX](PyMaterialX) : Core library Python API support + +# Shader Generation Support + +## Supported APIs +- C++ +- Python + +## Supported Backends + +- Core shader generation API: [MaterialXGenShader](MaterialXGenShader) module + - Refer to document in module folder. +- Shader Generation languages supported: + - GLSL + - Version 4 or higher + - Core support in [MaterialXGenGLSL](MaterialXGenGLSL) module + - Autodesk OGSFX Effect support in [MaterialXGenOgsFx](MaterialXGenOgsFx) module + - OSL: + - [MaterialXGenOsl](MaterialXGenOsl) module. + - Uses an implementation which differs from the reference OSL + implementation + +## Definition Libraries + +- Standard library ([stdlib](../documents/Libraries/stdlib)) implementations for supported languages. +- PBR library ([pbrlib](../documents/Libraries/pbrlib)): node definitions and implementations for supported languages. +- Basic "default" non-LUT color management transforms for OSL and + GLSL: + - lin_rec709, gamma18, gamma22, gamm24, acescg, srgb_texture +- Basic "light" definitions and implementations for GLSL: + - point, directional, spotlight, IBL + +## Library Tree Structure +- Refer to documentation in the [Libraries](../documents/Libraries) folder. + +## Unsupported definitions for Shader Generation + +Nodes and implementations which are not currently supported: +- ambientocclusion +- arrayappend +- curveadjust +- displacementshader and volumeshader and associated operations (add, + multiply, mix) +- mix surfaceshader for GLSL +- Matrix33 type operations for OSL. + +## Rendering Utilities + +- [MaterialXRender](MaterialXRender) module. +- Geometry handler with OBJ format support. +- Image handler with formats supported by the "stb" open source loader. +- Render test suite: Windows only. +- GLSL and OSL program validators + +## Test Framework + +The unit tests are located in the [MaterialXTest](MaterialXTest/README.md) module. + +This includes tests for core and shader generation. The tests executed are based on what build options have been enabled. The test suite for this resides [here](../documents/TestSuite). + +## Viewing Utilities + +- [MaterialXView](https://github.com/jstone-dev/MaterialX/blob/adsk_contrib/dev/README.md) module +- Sample material viewer which uses the core, shader generation and rendering utilities libraries. + +## Build Options +By default MaterialXCore, MaterialXFormat and MaterialXGenShader are built. +- Python support is enabled via the MATERIALX_BUILD_PYTHON build variable. +- OSL shader generation is enabled via the MATERIALX_BUILD_GEN_OSL build variable. +- GLSL shader generation is enabled via the MATERIALX_BUILD_GEN_GLSL build variable. +- OGSFX shader generation is enabled via the MATERIALX_BUILD_GEN_OGSFX build variable. The GLSL shader generation build variable must also be enabled. +- Building of rendering utilities is enabled via the MATERIALX_BUILD_RENDER build variable. Execution of rendering tests is enabled via the MATERIALX_TEST_RENDER. These tests are currently only supported for the Windows platform.