diff --git a/CMakeLists.txt b/CMakeLists.txt index 63ae3ab53c..63ed0d9b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ option(MATERIALX_BUILD_DOCS "Create HTML documentation using Doxygen. Requires t option(MATERIALX_BUILD_GEN_GLSL "Build the GLSL shader generator back-end." ON) option(MATERIALX_BUILD_GEN_OSL "Build the OSL shader generator back-end." ON) +option(MATERIALX_BUILD_GEN_OSL_NODES "Build the OSL nodes shader generator back-end." ON) option(MATERIALX_BUILD_GEN_MDL "Build the MDL shader generator back-end." ON) option(MATERIALX_BUILD_GEN_MSL "Build the MSL shader generator back-end." ON) option(MATERIALX_BUILD_RENDER "Build the MaterialX Render modules." ON) @@ -84,6 +85,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "iOS" OR CMAKE_SYSTEM_NAME MATCHES "tvOS" OR CMAKE set(MATERIALX_BUILD_GRAPH_EDITOR OFF) set(MATERIALX_BUILD_GEN_GLSL OFF) set(MATERIALX_BUILD_GEN_OSL OFF) + set(MATERIALX_BUILD_GEN_OSL_NODES OFF) set(MATERIALX_BUILD_GEN_MDL OFF) set(MATERIALX_BUILD_TESTS OFF) endif() @@ -103,6 +105,7 @@ if (MATERIALX_BUILD_APPLE_FRAMEWORK) endif() if (MATERIALX_BUILD_JS) + set(MATERIALX_BUILD_GEN_OSL_NODES OFF) set(MATERIALX_BUILD_RENDER OFF) set(MATERIALX_BUILD_TESTS OFF) endif() @@ -136,6 +139,12 @@ if(SKBUILD) set(MATERIALX_PYTHON_FOLDER_NAME "MaterialX") endif() +if (MATERIALX_BUILD_GEN_OSL_NODES) + set(MATERIALX_BUILD_GEN_OSL ON) + set(MATERIALX_BUILD_RENDER ON) + set(MATERIALX_BUILD_RENDER_PLATFORMS ON) +endif() + # Helpers for MDL validation if (MATERIALX_BUILD_GEN_MDL) set(MATERIALX_MDLC_EXECUTABLE "" CACHE FILEPATH "Full path to the mdlc binary.") @@ -162,6 +171,7 @@ set(MATERIALX_LIBNAME_SUFFIX "" CACHE STRING "Specify a suffix to all libraries mark_as_advanced(MATERIALX_BUILD_DOCS) mark_as_advanced(MATERIALX_BUILD_GEN_GLSL) mark_as_advanced(MATERIALX_BUILD_GEN_OSL) +mark_as_advanced(MATERIALX_BUILD_GEN_OSL_NODES) mark_as_advanced(MATERIALX_BUILD_GEN_MDL) mark_as_advanced(MATERIALX_BUILD_GEN_MSL) mark_as_advanced(MATERIALX_BUILD_RENDER) @@ -215,7 +225,7 @@ endif() # Allow the OSL CMake package to provide binary locations for render tests. # This will not override explicitly provided oslc, testrender, and include paths. -if(MATERIALX_BUILD_RENDER AND MATERIALX_BUILD_GEN_OSL AND MATERIALX_BUILD_TESTS) +if((MATERIALX_BUILD_RENDER AND MATERIALX_BUILD_GEN_OSL AND MATERIALX_BUILD_TESTS) OR MATERIALX_BUILD_GEN_OSL_NODES) find_package(OSL QUIET) if(OSL_FOUND) if(NOT MATERIALX_OSL_BINARY_OSLC) @@ -225,6 +235,10 @@ if(MATERIALX_BUILD_RENDER AND MATERIALX_BUILD_GEN_OSL AND MATERIALX_BUILD_TESTS) # OSL does not yet export a CMake target for testrender. set(MATERIALX_OSL_BINARY_TESTRENDER $/testrender) endif() + if(NOT MATERIALX_OSL_INCLUDE_PATH) + # OSL does not yet export a CMake target for testrender. + set(MATERIALX_OSL_INCLUDE_PATH $/../include) + endif() endif() endif() @@ -244,6 +258,10 @@ if (MATERIALX_BUILD_GEN_MDL) add_definitions(-DMATERIALX_INSTALL_MDL_MODULE_PATH=\"${MATERIALX_INSTALL_MDL_MODULE_PATH}\") endif() +if (MATERIALX_BUILD_GEN_OSL_NODES) + set(MATERIALX_BUILD_DATA_LIBRARY ON) +endif() + # Adjust the default installation path if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}/installed" CACHE PATH "Default install path" FORCE) @@ -449,7 +467,7 @@ add_subdirectory(source/MaterialXFormat) # Add shader generation subdirectories add_subdirectory(source/MaterialXGenShader) -if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MDL OR MATERIALX_BUILD_GEN_MSL) +if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_OSL_NODES OR MATERIALX_BUILD_GEN_MDL OR MATERIALX_BUILD_GEN_MSL) if (MATERIALX_BUILD_GEN_GLSL) add_definitions(-DMATERIALX_BUILD_GEN_GLSL) add_subdirectory(source/MaterialXGenGlsl) @@ -458,6 +476,10 @@ if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MD add_definitions(-DMATERIALX_BUILD_GEN_OSL) add_subdirectory(source/MaterialXGenOsl) endif() + if (MATERIALX_BUILD_GEN_OSL_NODES) + add_definitions(-DMATERIALX_BUILD_GEN_OSL_NODES) + add_subdirectory(source/MaterialXGenOslNodes) + endif() if (MATERIALX_BUILD_GEN_MDL) add_definitions(-DMATERIALX_BUILD_GEN_MDL) add_subdirectory(source/MaterialXGenMdl) diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index cc8139aef3..b41ae47855 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -28,6 +28,33 @@ if(MATERIALX_BUILD_DATA_LIBRARY) add_custom_target(MaterialXBuildData ALL DEPENDS ${MATERIALX_DATA_LIBRARY_BUILD_FILES}) + if (MATERIALX_BUILD_GEN_OSL_NODES AND MATERIALX_OSL_BINARY_OSLC) + + set(OSO_BUILD_PATH ${CMAKE_CURRENT_BINARY_DIR}/DataLibraryBuild/targets/genoslnodes/osos) + set(MTLX_BUILD_PATH ${CMAKE_CURRENT_BINARY_DIR}/DataLibraryBuild/targets/genoslnodes) + + set(SENTINEL_FILE ${CMAKE_CURRENT_BINARY_DIR}/buildosos.sentinel) + + add_custom_command( + OUTPUT ${SENTINEL_FILE} + COMMAND touch ${SENTINEL_FILE} + COMMAND cmake -E make_directory ${OSO_BUILD_PATH} + COMMAND cmake -E make_directory ${MTLX_BUILD_PATH} + COMMAND MaterialXGenOslNodes_LibsToOso + --outputOsoPath ${OSO_BUILD_PATH} + --outputMtlxPath ${MTLX_BUILD_PATH} + --oslCompilerPath ${MATERIALX_OSL_BINARY_OSLC} + --oslIncludePath ${MATERIALX_OSL_INCLUDE_PATH} + --libraryRelativeOsoPath libraries/targets/genoslnodes/osos + --removeNdPrefix true + DEPENDS ${MATERIALX_DATA_LIBRARY_SOURCE_FILES} MaterialXGenOslNodes_LibsToOso + ) + + add_custom_target(MaterialXBuild_genoslnodes_buildOsos ALL DEPENDS ${SENTINEL_FILE}) + add_dependencies(MaterialXBuildData MaterialXBuild_genoslnodes_buildOsos) + + endif() + set(DATA_LIBRARY_DIR ${DATA_LIBRARY_BUILD_DIR}) else() set(DATA_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/libraries/targets/genoslnodes.mtlx b/libraries/targets/genoslnodes.mtlx new file mode 100644 index 0000000000..88f7cd0e4c --- /dev/null +++ b/libraries/targets/genoslnodes.mtlx @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/python/Scripts/generateshader.py b/python/Scripts/generateshader.py old mode 100644 new mode 100755 index f8feb37d20..616be0d903 --- a/python/Scripts/generateshader.py +++ b/python/Scripts/generateshader.py @@ -92,6 +92,8 @@ def main(): gentarget = opts.target if gentarget == 'osl': shadergen = mx_gen_osl.OslShaderGenerator.create() + if gentarget == 'oslnodes': + shadergen = mx_gen_osl.OslNodeShaderGenerator.create() elif gentarget == 'mdl': shadergen = mx_gen_mdl.MdlShaderGenerator.create() elif gentarget == 'essl': diff --git a/resources/Materials/TestSuite/_options.mtlx b/resources/Materials/TestSuite/_options.mtlx index d9b2950b43..abf4b4d149 100644 --- a/resources/Materials/TestSuite/_options.mtlx +++ b/resources/Materials/TestSuite/_options.mtlx @@ -21,7 +21,7 @@ - + diff --git a/source/MaterialXGenOsl/OslShaderGenerator.cpp b/source/MaterialXGenOsl/OslShaderGenerator.cpp index bcae4f9ffb..c00b6db0b1 100644 --- a/source/MaterialXGenOsl/OslShaderGenerator.cpp +++ b/source/MaterialXGenOsl/OslShaderGenerator.cpp @@ -223,7 +223,9 @@ ShaderPtr OslShaderGenerator::createShader(const string& name, ElementPtr elemen const auto& outputSockets = graph->getOutputSockets(); const auto* singleOutput = outputSockets.size() == 1 ? outputSockets[0] : NULL; - const bool isSurfaceShaderOutput = singleOutput && singleOutput->getType() == Type::SURFACESHADER; + const bool isSurfaceShaderOutput = context.getOptions().oslImplicitSurfaceShaderConversion + && singleOutput && singleOutput->getType() == Type::SURFACESHADER; + if (isSurfaceShaderOutput) { graph->inlineNodeBeforeOutput(outputSockets[0], "_surfacematerial_", "ND_surfacematerial", "surfaceshader", "out", context); diff --git a/source/MaterialXGenOslNodes/CMakeLists.txt b/source/MaterialXGenOslNodes/CMakeLists.txt new file mode 100644 index 0000000000..1e8cf0c610 --- /dev/null +++ b/source/MaterialXGenOslNodes/CMakeLists.txt @@ -0,0 +1,46 @@ +file(GLOB_RECURSE materialx_source + "${CMAKE_CURRENT_SOURCE_DIR}/OslNodesShaderGenerator.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/OslNodesSyntax.cpp" +) +file(GLOB_RECURSE materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*") + +mx_add_library(MaterialXGenOslNodes + SOURCE_FILES + ${materialx_source} + HEADER_FILES + ${materialx_headers} + MTLX_MODULES + MaterialXGenShader + MaterialXCore + EXPORT_DEFINE + MATERIALX_GENOSLNODES_EXPORTS) + + +file(GLOB GenNodes_SRC "${CMAKE_CURRENT_SOURCE_DIR}/LibsToOso.cpp") + +set(MATERIALX_LIBRARIES + MaterialXCore + MaterialXFormat + MaterialXGenShader + MaterialXGenOsl + MaterialXRenderOsl) + +add_executable(MaterialXGenOslNodes_LibsToOso ${GenNodes_SRC}) + +target_link_libraries( + MaterialXGenOslNodes_LibsToOso + ${MATERIALX_LIBRARIES}) + +set_target_properties( + MaterialXGenOslNodes_LibsToOso PROPERTIES + INSTALL_RPATH "${MATERIALX_UP_ONE_RPATH}") + +# TODO: We likely want to install that file elsewhere and not under `bin`, +# if at all, as we maybe want to keep this executable available at build time only. +install(TARGETS MaterialXGenOslNodes_LibsToOso + EXPORT MaterialX + RUNTIME DESTINATION ${MATERIALX_INSTALL_BIN_PATH}) +if(MSVC) + install(FILES $ + DESTINATION ${MATERIALX_INSTALL_BIN_PATH} OPTIONAL) +endif() diff --git a/source/MaterialXGenOslNodes/Export.h b/source/MaterialXGenOslNodes/Export.h new file mode 100644 index 0000000000..af0630ec22 --- /dev/null +++ b/source/MaterialXGenOslNodes/Export.h @@ -0,0 +1,22 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_GENOSLNODES_EXPORT_H +#define MATERIALX_GENOSLNODES_EXPORT_H + +#include + +/// @file +/// Macros for declaring imported and exported symbols. + +#if defined(MATERIALX_GENOSLNODES_EXPORTS) + #define MX_GENOSLNODES_API MATERIALX_SYMBOL_EXPORT + #define MX_GENOSLNODES_EXTERN_TEMPLATE(...) MATERIALX_EXPORT_EXTERN_TEMPLATE(__VA_ARGS__) +#else + #define MX_GENOSLNODES_API MATERIALX_SYMBOL_IMPORT + #define MX_GENOSLNODES_EXTERN_TEMPLATE(...) MATERIALX_IMPORT_EXTERN_TEMPLATE(__VA_ARGS__) +#endif + +#endif diff --git a/source/MaterialXGenOslNodes/LibsToOso.cpp b/source/MaterialXGenOslNodes/LibsToOso.cpp new file mode 100644 index 0000000000..154971dd65 --- /dev/null +++ b/source/MaterialXGenOslNodes/LibsToOso.cpp @@ -0,0 +1,450 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +namespace mx = MaterialX; + + +const std::string setCiOslNodeSource = R"( + +#include "mx_funcs.h" + +#define true 1 +#define false 0 +struct textureresource { string filename; string colorspace; }; +#define BSDF closure color +#define EDF closure color +#define VDF closure color +struct surfaceshader { closure color bsdf; closure color edf; float opacity; }; +#define volumeshader closure color +#define displacementshader vector +#define lightshader closure color +#define MATERIAL closure color + +#define M_FLOAT_EPS 1e-8 +closure color null_closure() { closure color null_closure = 0; return null_closure; } + +shader setCi ( + float float_input = 0, + color color3_input = 0, + color4 color4_input = {0,0}, + vector2 vector2_input = {0,0}, + vector vector3_input = 0, + vector4 vector4_input = {0,0}, + surfaceshader surfaceshader_input = {0,0,0}, + BSDF BSDF_input = 0, + EDF EDF_input = 0, + MATERIAL material_input = 0, + + output closure color Out_Ci = 0 +) +{ + color c = 0; + float a = 1; + + if (isconnected(surfaceshader_input)) { + + float opacity_weight = clamp(surfaceshader_input.opacity, 0.0, 1.0); + Out_Ci = (surfaceshader_input.bsdf + surfaceshader_input.edf) * opacity_weight + transparent() * (1.0 - opacity_weight); + + } else if (isconnected(material_input)) { + Out_Ci = material_input; + } else if (isconnected(BSDF_input)) { + Out_Ci = BSDF_input; + } else if (isconnected(EDF_input)) { + Out_Ci = EDF_input; + } else { + if (isconnected(float_input)) { + c = float_input; + } else if (isconnected(color3_input)) { + c = color3_input; + } else if (isconnected(color4_input)) { + c = color4_input.rgb; + a = color4_input.a; + } else if (isconnected(vector2_input)) { + c = color(vector2_input.x, vector2_input.y, 0); + } else if (isconnected(vector3_input)) { + c = color(vector3_input); + } else if (isconnected(vector4_input)) { + c = color(vector4_input.x, vector4_input.y, vector4_input.z); + a = vector4_input.w; + } + Out_Ci = c * a * emission() + (1-a) * transparent(); + } + + Ci = Out_Ci; +} +)"; + + + +const std::string options = + " Options: \n" + " --outputOsoPath [DIRPATH] TODO\n" + " --libraryRelativeOsoPath [DIRPATH] TODO\n" + " --outputMtlxPath [DIRPATH] TODO\n" + " --oslCompilerPath [FILEPATH] TODO\n" + " --oslIncludePath [DIRPATH] TODO\n" + " --libraries [STRING] TODO\n" + " --removeNdPrefix [BOOLEAN] TODO\n" + " --prefix [STRING] TODO\n" + " --help Display the complete list of command-line options\n"; + +template void parseToken(std::string token, std::string type, T& res) +{ + if (token.empty()) + return; + + mx::ValuePtr value = mx::Value::createValueFromStrings(token, type); + + if (!value) + { + std::cout << "Unable to parse token " << token << " as type " << type << std::endl; + + return; + } + + res = value->asA(); +} + +int main(int argc, char* const argv[]) +{ + std::vector tokens; + + // Gather the provided arguments. + for (int i = 1; i < argc; i++) + { + tokens.emplace_back(argv[i]); + } + + std::string argOutputOsoPath; + std::string argOutputMtlxPath; + std::string argLibraryRelativeOsoPath; + std::string argOslCompilerPath; + std::string argOslIncludePath; + std::string argLibraries; + bool argRemoveNdPrefix = false; + std::string argPrefix; + + // Loop over the provided arguments, and store their associated values. + for (size_t i = 0; i < tokens.size(); i++) + { + const std::string& token = tokens[i]; + const std::string& nextToken = i + 1 < tokens.size() ? tokens[i + 1] : mx::EMPTY_STRING; + + if (token == "--outputOsoPath") + argOutputOsoPath = nextToken; + else if (token == "--outputMtlxPath") + argOutputMtlxPath = nextToken; + else if (token == "--libraryRelativeOsoPath") + argLibraryRelativeOsoPath = nextToken; + else if (token == "--oslCompilerPath") + argOslCompilerPath = nextToken; + else if (token == "--oslIncludePath") + argOslIncludePath = nextToken; + else if (token == "--libraries") + argLibraries = nextToken; + else if (token == "--removeNdPrefix") + parseToken(nextToken, "boolean", argRemoveNdPrefix); + else if (token == "--prefix") + argPrefix = nextToken; + else if (token == "--help") + { + std::cout << "MaterialXGenOslNodes - LibsToOso version " << mx::getVersionString() << std::endl; + std::cout << options << std::endl; + + return 0; + } + else + { + std::cout << "Unrecognized command-line option: " << token << std::endl; + std::cout << "Launch the graph editor with '--help' for a complete list of supported " + "options." + << std::endl; + + continue; + } + + if (nextToken.empty()) + std::cout << "Expected another token following command-line option: " << token << std::endl; + else + i++; + } + + // Ensure we have a valid output path. + mx::FilePath outputOsoPath(argOutputOsoPath); + + if (!outputOsoPath.exists() || !outputOsoPath.isDirectory()) + { + outputOsoPath.createDirectory(); + + if (!outputOsoPath.exists() || !outputOsoPath.isDirectory()) + { + std::cerr << "Failed to find and/or create the provided output oso " + "path: " + << outputOsoPath.asString() << std::endl; + + return 1; + } + } + + mx::FilePath outputMtlxPath(argOutputMtlxPath); + if (!outputMtlxPath.exists() || !outputMtlxPath.isDirectory()) + { + outputMtlxPath.createDirectory(); + + if (!outputMtlxPath.exists() || !outputMtlxPath.isDirectory()) + { + std::cerr << "Failed to find and/or create the provided output Mtlx " + "path: " + << outputMtlxPath.asString() << std::endl; + + return 1; + } + } + + // Ensure we have a valid path to the OSL compiler. + mx::FilePath oslCompilerPath(argOslCompilerPath); + + if (!oslCompilerPath.exists()) + { + std::cerr << "The provided path to the OSL compiler is not valid: " << oslCompilerPath.asString() << std::endl; + + return 1; + } + + // Ensure we have a valid path to the OSL includes. + mx::FilePath oslIncludePath(argOslIncludePath); + + if (!oslIncludePath.exists() || !oslIncludePath.isDirectory()) + { + std::cerr << "The provided path to the OSL includes is not valid: " << oslIncludePath.asString() << std::endl; + + return 1; + } + + // Create the libraries search path and document. + mx::FileSearchPath librariesSearchPath = mx::getDefaultDataSearchPath(); + mx::DocumentPtr librariesDoc = mx::createDocument(); + + // If a list of comma separated libraries was provided, load them individually into our document. + if (!argLibraries.empty()) + { + // TODO: Should we check that we actually split something based on the separator, just to be sure? + const mx::StringVec& librariesVec = mx::splitString(argLibraries, ","); + mx::FilePathVec librariesPaths{ "libraries/targets" }; + + for (const std::string& library : librariesVec) + librariesPaths.emplace_back("libraries/" + library); + + loadLibraries(librariesPaths, librariesSearchPath, librariesDoc); + } + // Otherwise, simply load all the available libraries. + else + loadLibraries({ "libraries" }, librariesSearchPath, librariesDoc); + + const std::string target = "genoslnodes"; + mx::FilePath implMtlxDocFilePath = outputMtlxPath / "genoslnodes_impl.mtlx"; + mx::DocumentPtr implMtlxDoc = mx::createDocument(); + + // Create and setup the `OslRenderer` that will be used to both generate the `.osl` files as well as compile + // them to `.oso` files. + mx::OslRendererPtr oslRenderer = mx::OslRenderer::create(); + oslRenderer->setOslCompilerExecutable(oslCompilerPath); + + // Build the list of include paths that will be passed to the `OslRenderer`. + mx::FileSearchPath oslRendererIncludePaths; + + // Add the provided OSL include path. + oslRendererIncludePaths.append(oslIncludePath); + // Add the MaterialX's OSL include path. + oslRendererIncludePaths.append(librariesSearchPath.find("libraries/stdlib/genosl/include")); + + oslRenderer->setOslIncludePath(oslRendererIncludePaths); + + // Create the OSL shader generator. + mx::ShaderGeneratorPtr oslShaderGen = mx::OslShaderGenerator::create(); + + // Register types from the libraries on the OSL shader generator. + oslShaderGen->registerTypeDefs(librariesDoc); + + // Setup the context of the OSL shader generator. + mx::GenContext context(oslShaderGen); + context.registerSourceCodeSearchPath(librariesSearchPath); + // TODO: It might be good to find a way to not hardcode these options, especially the texture flip. + context.getOptions().addUpstreamDependencies = false; + context.getOptions().fileTextureVerticalFlip = false; + context.getOptions().oslImplicitSurfaceShaderConversion = false; + + // We'll use this boolean to return an error code is one of the `NodeDef` failed to codegen/compile. + bool hasFailed = false; + try + { + const std::string& oslFilePath = (outputOsoPath / "setCi.osl").asString(); + std::ofstream oslFile; + + // TODO: Check that we have a valid/opened file descriptor before doing anything with it? + oslFile.open(oslFilePath); + // Dump the content of the codegen'd `NodeDef` to our `.osl` file. + oslFile << setCiOslNodeSource; + oslFile.close(); + + // Compile the `.osl` file to a `.oso` file next to it. + oslRenderer->compileOSL(oslFilePath); + } + // Catch any codegen/compilation related exceptions. + catch (mx::ExceptionRenderError& exc) + { + std::cout << "Encountered a codegen/compilation related exception for the " + "following node: " << std::endl; + std::cout << exc.what() << std::endl; + + // Dump details about the exception in the log file. + for (const std::string& error : exc.errorLog()) + { + std::cout << error << std::endl; + } + + hasFailed = true; + } + // Catch any other exceptions + catch (mx::Exception& exc) + { + std::cout << "Failed to codegen/compile the following node to OSL: " << std::endl; + std::cout << exc.what() << std::endl; + + hasFailed = true; + } + + + // We create and use a dedicated `NodeGraph` to avoid `NodeDef` names collision. + mx::NodeGraphPtr librariesDocGraph = librariesDoc->addNodeGraph("librariesDocGraph"); + + // Loop over all the `NodeDef` gathered in our documents from the provided libraries. + for (const mx::NodeDefPtr& nodeDef : librariesDoc->getNodeDefs()) + { + std::string nodeName = nodeDef->getName(); + + // Remove the "ND_" prefix from a valid `NodeDef` name. + if (argRemoveNdPrefix) + { + if (nodeName.size() > 3 && nodeName.substr(0, 3) == "ND_") + nodeName = nodeName.substr(3); + + // Add a prefix to the shader's name, both in the filename as well as inside the shader itself. + if (!argPrefix.empty()) + nodeName = argPrefix + "_" + nodeName; + } + + // Determine whether or not there's a valid implementation of the current `NodeDef` for the type associated + // to our OSL shader generator, i.e. OSL, and if not, skip it. + mx::InterfaceElementPtr nodeImpl = nodeDef->getImplementation(oslShaderGen->getTarget()); + + if (!nodeImpl) + { + std::cout << "The following `NodeDef` does not provide a valid OSL implementation, " + "and will be skipped: " + << nodeDef->getName() << std::endl; + + continue; + } + + // TODO: Check for the existence/validity of the `Node`? + mx::NodePtr node = librariesDoc->addNodeInstance(nodeDef, nodeName); + + std::string oslShaderName = node->getName(); + oslShaderGen->getSyntax().makeValidName(oslShaderName); + + const std::string oslFileName = oslShaderName + ".osl"; + const std::string& oslFilePath = (outputOsoPath / oslFileName).asString(); + std::ofstream oslFile; + + // Codegen the `Node` to an `.osl` file. + try + { + // Codegen the `Node` to OSL. + mx::ShaderPtr oslShader = oslShaderGen->generate(node->getName(), node, context); + + // TODO: Check that we have a valid/opened file descriptor before doing anything with it? + oslFile.open(oslFilePath); + // Dump the content of the codegen'd `NodeDef` to our `.osl` file. + oslFile << oslShader->getSourceCode(); + oslFile.close(); + } + // Catch any codegen/compilation related exceptions. + catch (mx::ExceptionShaderGenError& exc) + { + std::cerr << "Encountered a shader codegen related exception for the " + "following node: " + << nodeDef->getName() << std::endl; + std::cerr << exc.what() << std::endl; + + hasFailed = true; + } + + // Compile the codegen'd `.osl` file. + try + { + // Compile the `.osl` file to a `.oso` file next to it. + oslRenderer->compileOSL(oslFilePath); + + { + std::string implName = "IMPL_" + nodeName + "_" + target; + auto impl = implMtlxDoc->addImplementation(implName); + impl->setNodeDef(nodeDef); + // TODO: stash the the OSO path here. + // This is writing the absolute path - which we don't want + impl->setFile(argLibraryRelativeOsoPath); + + impl->setFunction(oslShaderName); + impl->setAttribute("sourcecode", "dummy"); + impl->setTarget(target); + } + } + // Catch any codegen/compilation related exceptions. + catch (mx::ExceptionRenderError& exc) + { + std::cerr << "Encountered a shader compilation related exception for the " + "following node: " + << nodeDef->getName() << std::endl; + std::cerr << exc.what() << std::endl; + + // Dump details about the exception in the log file. + for (const std::string& error : exc.errorLog()) + std::cerr << error << std::endl; + + hasFailed = true; + } + } + + mx::writeToXmlFile(implMtlxDoc, implMtlxDocFilePath); + + // If something went wrong, return an appropriate error code. + if (hasFailed) + { + std::cerr << "Failed to codegen and compile all the OSL shaders associated to the provided MaterialX " + "libraries, see the log file for more details." + << std::endl; + + return 1; + } + + return 0; +} diff --git a/source/MaterialXGenOslNodes/OslNodesShaderGenerator.cpp b/source/MaterialXGenOslNodes/OslNodesShaderGenerator.cpp new file mode 100644 index 0000000000..afb26c429b --- /dev/null +++ b/source/MaterialXGenOslNodes/OslNodesShaderGenerator.cpp @@ -0,0 +1,218 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include +#include +#include +#include +#include + + +MATERIALX_NAMESPACE_BEGIN + +const string OslNodesShaderGenerator::TARGET = "genoslnodes"; + +// +// OslNodesShaderGenerator methods +// + +OslNodesShaderGenerator::OslNodesShaderGenerator(TypeSystemPtr typeSystem) : + ShaderGenerator(typeSystem, OslNodesSyntax::create(typeSystem)) +{ +} + +static string paramString(const string& paramType, const string& paramName, const string& paramValue) +{ + return "param " + paramType + " " + paramName + " " + paramValue + " ;"; +} + +static string connectString(const string& fromNode, const string& fromName, const string& toNode, const string& toName) +{ + return "connect " + fromNode + "." + fromName + " " + toNode + "." + toName + " ;"; +} + +ShaderPtr OslNodesShaderGenerator::generate(const string& name, ElementPtr element, GenContext& context) const +{ + ShaderPtr shader = createShader(name, element, context); + ShaderGraph& graph = shader->getGraph(); + ShaderStage& stage = shader->getStage(Stage::PIXEL); + + ConstDocumentPtr document = element->getDocument(); + + string lastNodeName; + ShaderOutput* lastOutput = nullptr; + std::vector connections; + + std::set osoPaths; + + // Walk the node graph, emitting shaders and param declarations. + for (auto&& node : graph.getNodes()) { + const string& nodeName = node->getName(); + + for (auto&& input : node->getInputs()) { + string inputName = input->getName(); + _syntax->makeValidName(inputName); + + const ShaderOutput* connection = input->getConnection(); + if (!connection || connection->getNode() == &graph) { + if (input->isDefault()) + continue; + + if (input->getName() == "backsurfaceshader" + || input->getName() == "displacementshader") + continue; // FIXME: these aren't getting pruned by isDefault + + string value = _syntax->getValue(input); + if (value == "null_closure()") + continue; + + auto inputType = input->getType(); + if (inputType == Type::VECTOR2) + { + auto parts = splitString(value, " "); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".x", parts[0]), stage, false); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".y", parts[1]), stage, false); + } + else if (inputType == Type::VECTOR4) + { + auto parts = splitString(value, " "); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".x", parts[0]), stage, false); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".y", parts[1]), stage, false); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".z", parts[2]), stage, false); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".w", parts[3]), stage, false); + } + else if (inputType == Type::COLOR4) + { + auto parts = splitString(value, " "); + emitLine(paramString(_syntax->getTypeName(Type::COLOR3), inputName+".rgb", parts[0] + " " + parts[1] + " " + parts[2]), stage, false); + emitLine(paramString(_syntax->getTypeName(Type::FLOAT), inputName+".a", parts[3]), stage, false); + } + else + { + emitLine(paramString(_syntax->getTypeName(input->getType()), inputName, value), stage, false); + } + } else { + string connName = connection->getName(); + _syntax->makeValidName(connName); + + string connect = connectString(connection->getNode()->getName(), connName, nodeName, inputName); + // Save connect emits for the end, because they can't come + // before both connected shaders have been declared. + connections.push_back(connect); + } + } + + // Keep track of the root output, so we can connect it to our setCi node + lastOutput = node->getOutput(0); + + NodeDefPtr nodeDef = document->getNodeDef(node->getNodeDefName()); + ImplementationPtr impl = nodeDef->getImplementation("genoslnodes")->asA(); + + if (!impl) + { + printf("Skipping test due to missing implementation\n"); + return nullptr; + } + + string osoName = impl->getFunction(); + + string osoPath = impl->getFile(); + osoPaths.insert(osoPath); + + emitLine("shader " + osoName + " " + nodeName + " ;", stage, false); + lastNodeName = nodeName; + } + + if (!lastOutput) { + printf("Invalid shader\n"); + return nullptr; + } + + for (auto&& connect : connections) { + emitLine(connect, stage, false); + } + + // During unit tests, wrap a special node that will add the output to Ci. + if (context.getOptions().oslNodesConnectCiWrapper) { + emitLine("shader setCi root ;", stage, false); + string connect = connectString( + lastNodeName, + lastOutput->getName(), + "root", + lastOutput->getType().getName() + "_input" + ); + emitLine(connect, stage, false); + } + + // From our set of required oso paths, build the path string that oslc will need. + string osoPathStr; + string separator = ""; + for (const auto& osoPath : osoPaths) + { + auto fullOsoPath = context.resolveSourceFile(osoPath, ""); + auto fullOsoPathStr = fullOsoPath.asString(); + + osoPathStr += separator + fullOsoPathStr; + separator = ","; + } + + shader->setAttribute("osoPath", Value::createValue(osoPathStr)); + + return shader; +} + + +ShaderPtr OslNodesShaderGenerator::createShader(const string& name, ElementPtr element, GenContext& context) const +{ + // Create the root shader graph + ShaderGraphPtr graph = ShaderGraph::create(nullptr, name, element, context); + ShaderPtr shader = std::make_shared(name, graph); + + // Create our stage. + ShaderStagePtr stage = createStage(Stage::PIXEL, *shader); + stage->createUniformBlock(OSLNodes::UNIFORMS); + stage->createInputBlock(OSLNodes::INPUTS); + stage->createOutputBlock(OSLNodes::OUTPUTS); + + // Create shader variables for all nodes that need this. + createVariables(graph, context, *shader); + + // Create uniforms for the published graph interface. + VariableBlock& uniforms = stage->getUniformBlock(OSLNodes::UNIFORMS); + for (ShaderGraphInputSocket* inputSocket : graph->getInputSockets()) + { + // Only for inputs that are connected/used internally, + // and are editable by users. + if (inputSocket->getConnections().size() && graph->isEditable(*inputSocket)) + { + uniforms.add(inputSocket->getSelf()); + } + } + + // Create outputs from the graph interface. + VariableBlock& outputs = stage->getOutputBlock(OSLNodes::OUTPUTS); + for (ShaderGraphOutputSocket* outputSocket : graph->getOutputSockets()) + { + outputs.add(outputSocket->getSelf()); + } + + return shader; +} + + +namespace OSLNodes +{ + +// Identifiers for OSL variable blocks +const string UNIFORMS = "u"; +const string INPUTS = "i"; +const string OUTPUTS = "o"; + +} // namespace OSL + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXGenOslNodes/OslNodesShaderGenerator.h b/source/MaterialXGenOslNodes/OslNodesShaderGenerator.h new file mode 100644 index 0000000000..3dd444d8cb --- /dev/null +++ b/source/MaterialXGenOslNodes/OslNodesShaderGenerator.h @@ -0,0 +1,67 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_OSLNODESSHADERGENERATOR_H +#define MATERIALX_OSLNODESSHADERGENERATOR_H + +/// @file +/// OSL shading language generator + +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +using OslNodesShaderGeneratorPtr = shared_ptr; + +/// @class OslNodesShaderGenerator +/// Base class for OSL (Open Shading Language) shader generators. +/// A generator for a specific OSL target should be derived from this class. +class MX_GENOSLNODES_API OslNodesShaderGenerator : public ShaderGenerator +{ + public: + /// Constructor. + OslNodesShaderGenerator(TypeSystemPtr typeSystem); + + /// Creator function. + /// If a TypeSystem is not provided it will be created internally. + /// Optionally pass in an externally created TypeSystem here, + /// if you want to keep type descriptions alive after the lifetime + /// of the shader generator. + static ShaderGeneratorPtr create(TypeSystemPtr typeSystem = nullptr) + { + return std::make_shared(typeSystem ? typeSystem : TypeSystem::create()); + } + + /// Return a unique identifier for the target this generator is for + const string& getTarget() const override { return TARGET; } + + /// Generate a shader starting from the given element, translating + /// the element and all dependencies upstream into shader code. + ShaderPtr generate(const string& name, ElementPtr element, GenContext& context) const override; + + /// Unique identifier for this generator target + static const string TARGET; + + protected: + /// Create and initialize a new OSL shader for shader generation. + virtual ShaderPtr createShader(const string& name, ElementPtr element, GenContext& context) const; + +}; + +namespace OSLNodes +{ + +/// Identifiers for OSL variable blocks +extern MX_GENOSLNODES_API const string UNIFORMS; +extern MX_GENOSLNODES_API const string INPUTS; +extern MX_GENOSLNODES_API const string OUTPUTS; + +} // namespace OSL + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXGenOslNodes/OslNodesSyntax.cpp b/source/MaterialXGenOslNodes/OslNodesSyntax.cpp new file mode 100644 index 0000000000..1bb15144f3 --- /dev/null +++ b/source/MaterialXGenOslNodes/OslNodesSyntax.cpp @@ -0,0 +1,464 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +namespace +{ + +template +class OslNodesVectorTypeSyntax : public AggregateTypeSyntax +{ + public: + OslNodesVectorTypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue, + const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, + const StringVec& members = EMPTY_MEMBERS) : + AggregateTypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members) + { + } + + string getValue(const Value& value, bool /*uniform*/) const override + { + // param string values are space-separated, not comma-separated + T c = value.asA(); + + string result = ""; + string separator = ""; + for (size_t i = 0; i < c.numElements(); i++) { + result += separator; + result += toValueString(c[i]); + + separator = " "; + } + + return result; + + } +}; + +class OslBooleanTypeSyntax : public ScalarTypeSyntax +{ + public: + OslBooleanTypeSyntax(const Syntax* parent) : + ScalarTypeSyntax(parent, "int", "0", "0", EMPTY_STRING, "#define true 1\n#define false 0") + { + } + + string getValue(const Value& value, bool /*uniform*/) const override + { + return value.asA() ? "1" : "0"; + } +}; + +class OslArrayTypeSyntax : public ScalarTypeSyntax +{ + public: + OslArrayTypeSyntax(const Syntax* parent, const string& name) : + ScalarTypeSyntax(parent, name, EMPTY_STRING, EMPTY_STRING, EMPTY_STRING) + { + } + + string getValue(const Value& value, bool uniform) const override + { + if (!isEmpty(value)) + { + return "{" + value.getValueString() + "}"; + } + // OSL disallows arrays without initialization when specified as input uniform + else if (uniform) + { + throw ExceptionShaderGenError("Uniform array cannot initialize to a empty value."); + } + return EMPTY_STRING; + } + + protected: + virtual bool isEmpty(const Value& value) const = 0; +}; + +class OslFloatArrayTypeSyntax : public OslArrayTypeSyntax +{ + public: + explicit OslFloatArrayTypeSyntax(const Syntax* parent, const string& name) : + OslArrayTypeSyntax(parent, name) + { + } + + protected: + bool isEmpty(const Value& value) const override + { + vector valueArray = value.asA>(); + return valueArray.empty(); + } +}; + +class OslIntegerArrayTypeSyntax : public OslArrayTypeSyntax +{ + public: + explicit OslIntegerArrayTypeSyntax(const Syntax* parent, const string& name) : + OslArrayTypeSyntax(parent, name) + { + } + + protected: + bool isEmpty(const Value& value) const override + { + vector valueArray = value.asA>(); + return valueArray.empty(); + } +}; + +// In OSL vector2, vector4, 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 Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue, + const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, + const StringVec& members = EMPTY_MEMBERS) : + AggregateTypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members) + { + } + + string getValue(const Value& value, bool uniform) const override + { + if (uniform) + { + return "{" + value.getValueString() + "}"; + } + else + { + return getName() + "(" + value.getValueString() + ")"; + } + } +}; + +class OSLMatrix3TypeSyntax : public AggregateTypeSyntax +{ + public: + OSLMatrix3TypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue, + const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, + const StringVec& members = EMPTY_MEMBERS) : + AggregateTypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members) + { + } + + string getValue(const Value& value, bool /*uniform*/) const override + { + StringVec values = splitString(value.getValueString(), ","); + if (values.empty()) + { + throw ExceptionShaderGenError("No values given to construct a value"); + } + + // Write the value using a stream to maintain any float formatting set + // using Value::setFloatFormat() and Value::setFloatPrecision() + StringStream ss; + ss << getName() << "("; + for (size_t i = 0; i < values.size(); i++) + { + ss << values[i] << ", "; + if ((i + 1) % 3 == 0) + { + ss << "0.000" + << ", "; + } + } + static string ROW_4("0.000, 0.000, 0.000, 1.000"); + ss << ROW_4 << ")"; + + return ss.str(); + } +}; + +class OSLFilenameTypeSyntax : public AggregateTypeSyntax +{ + public: + OSLFilenameTypeSyntax(const Syntax* parent, const string& name, const string& defaultValue, const string& uniformDefaultValue, + const string& typeAlias = EMPTY_STRING, const string& typeDefinition = EMPTY_STRING, + const StringVec& members = EMPTY_MEMBERS) : + AggregateTypeSyntax(parent, name, defaultValue, uniformDefaultValue, typeAlias, typeDefinition, members) + { + } + + string getValue(const ShaderPort* port, bool /*uniform*/) const override + { + if (!port) + { + return EMPTY_STRING; + } + + const string filename = port->getValue() ? port->getValue()->getValueString() : EMPTY_STRING; + return filename; + } + + string getValue(const Value& value, bool /*uniform*/) const override + { + return value.getValueString(); + } +}; + +} // anonymous namespace + +const string OslNodesSyntax::OUTPUT_QUALIFIER = "output"; +const string OslNodesSyntax::SOURCE_FILE_EXTENSION = ".osl"; +const StringVec OslNodesSyntax::VECTOR_MEMBERS = { "[0]", "[1]", "[2]" }; +const StringVec OslNodesSyntax::VECTOR2_MEMBERS = { ".x", ".y" }; +const StringVec OslNodesSyntax::VECTOR4_MEMBERS = { ".x", ".y", ".z", ".w" }; +const StringVec OslNodesSyntax::COLOR4_MEMBERS = { ".rgb[0]", ".rgb[1]", ".rgb[2]", ".a" }; + +// +// OslNodesSyntax methods +// + +OslNodesSyntax::OslNodesSyntax(TypeSystemPtr typeSystem) : Syntax(typeSystem) +{ + // Add in all reserved words and keywords in OSL + registerReservedWords( + { // OSL types and keywords + "and", "break", "closure", "color", "continue", "do", "else", "emit", "float", "for", "if", "illuminance", + "illuminate", "int", "matrix", "normal", "not", "or", "output", "point", "public", "return", "string", + "struct", "vector", "void", "while", + "bool", "case", "catch", "char", "class", "const", "delete", "default", "double", "enum", "extern", + "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", + // OSL standard library functions names + "degrees", "radians", "cos", "sin", "tan", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", + "pow", "log", "log2", "log10", "logb", "sqrt", "inversesqrt", "cbrt", "hypot", "abs", "fabs", "sign", + "floor", "ceil", "round", "trunc", "fmod", "mod", "min", "max", "clamp", "mix", "select", "isnan", + "isinf", "isfinite", "erf", "erfc", "cross", "dot", "length", "distance", "normalize", "faceforward", + "reflect", "fresnel", "transform", "transformu", "rotate", "luminance", "blackbody", "wavelength_color", + "transformc", "determinant", "transpose", "step", "smoothstep", "linearstep", "smooth_linearstep", "aastep", + "hash", "strlen", "getchar", "startswith", "endswith", "substr", "stof", "stoi", "concat", "textureresource", + "backfacing", "raytype", "iscameraray", "isdiffuseray", "isglossyray", "isshadowray", "getmatrix", + "emission", "background", "diffuse", "oren_nayer", "translucent", "phong", "ward", "microfacet", + "reflection", "transparent", "debug", "holdout", "subsurface", "sheen", + "oren_nayar_diffuse_bsdf", "burley_diffuse_bsdf", "dielectric_bsdf", "conductor_bsdf", "generalized_schlick_bsdf", + "translucent_bsdf", "transparent_bsdf", "subsurface_bssrdf", "sheen_bsdf", "uniform_edf", "anisotropic_vdf", + "medium_vdf", "layer", "artistic_ior" }); + + // + // Register type syntax handlers for each data type. + // + + registerTypeSyntax( + Type::FLOAT, + std::make_shared( + this, + "float", + "0.0", + "0.0")); + + registerTypeSyntax( + Type::FLOATARRAY, + std::make_shared( + this, + "float")); + + registerTypeSyntax( + Type::INTEGER, + std::make_shared( + this, + "int", + "0", + "0")); + + registerTypeSyntax( + Type::INTEGERARRAY, + std::make_shared( + this, + "int")); + + registerTypeSyntax( + Type::BOOLEAN, + std::make_shared(this)); + + registerTypeSyntax( + // Note: the color type in OSL is a built in type and + // should not use the custom OslStructTypeSyntax. + Type::COLOR3, + std::make_shared>( + this, + "color", + "color(0.0)", + "color(0.0)", + EMPTY_STRING, + EMPTY_STRING, + VECTOR_MEMBERS)); + + registerTypeSyntax( + Type::COLOR4, + std::make_shared>( + this, + "color", + "color(0.0)", + "color(0.0)", + EMPTY_STRING, + EMPTY_STRING, + VECTOR4_MEMBERS)); + + registerTypeSyntax( + Type::VECTOR2, + std::make_shared>( + this, + "vector2", + "vector2(0.0, 0.0)", + "{0.0, 0.0}", + EMPTY_STRING, + EMPTY_STRING, + VECTOR2_MEMBERS)); + + registerTypeSyntax( + // Note: the vector type in OSL is a built in type and + // should not use the custom OslStructTypeSyntax. + Type::VECTOR3, + std::make_shared>( + this, + "vector", + "vector(0.0)", + "vector(0.0)", + EMPTY_STRING, + EMPTY_STRING, + VECTOR_MEMBERS)); + + registerTypeSyntax( + Type::VECTOR4, + std::make_shared>( + this, + "vector4", + "vector4(0.0, 0.0, 0.0, 0.0)", + "{0.0, 0.0, 0.0, 0.0}", + EMPTY_STRING, + EMPTY_STRING, + VECTOR4_MEMBERS)); + + registerTypeSyntax( + Type::MATRIX33, + std::make_shared( + this, + "matrix", + "matrix(1.0)", + "matrix(1.0)")); + + registerTypeSyntax( + Type::MATRIX44, + std::make_shared( + this, + "matrix", + "matrix(1.0)", + "matrix(1.0)")); + + registerTypeSyntax( + Type::STRING, + std::make_shared( + this, + "string", + "\"\"", + "\"\"")); + + registerTypeSyntax( + Type::FILENAME, + std::make_shared( + this, + "string", + "textureresource (\"\", \"\")", + "(\"\", \"\")", + EMPTY_STRING, + "struct textureresource { string filename; string colorspace; };")); + + registerTypeSyntax( + Type::BSDF, + std::make_shared( + this, + "BSDF", + "null_closure()", + "0", + "closure color", + "#define BSDF closure color")); + + registerTypeSyntax( + Type::EDF, + std::make_shared( + this, + "EDF", + "null_closure()", + "0", + "closure color", + "#define EDF closure color")); + + registerTypeSyntax( + Type::VDF, + std::make_shared( + this, + "VDF", + "null_closure()", + "0", + "closure color", + "#define VDF closure color")); + + registerTypeSyntax( + Type::SURFACESHADER, + std::make_shared( + this, + "surfaceshader", + "surfaceshader(null_closure(), null_closure(), 1.0)", + "{ 0, 0, 1.0 }", + "closure color", + "struct surfaceshader { closure color bsdf; closure color edf; float opacity; };")); + + registerTypeSyntax( + Type::VOLUMESHADER, + std::make_shared( + this, + "volumeshader", + "null_closure()", + "0", + "closure color", + "#define volumeshader closure color")); + + registerTypeSyntax( + Type::DISPLACEMENTSHADER, + std::make_shared( + this, + "displacementshader", + "vector(0.0)", + "vector(0.0)", + "vector", + "#define displacementshader vector")); + + registerTypeSyntax( + Type::LIGHTSHADER, + std::make_shared( + this, + "lightshader", + "null_closure()", + "0", + "closure color", + "#define lightshader closure color")); + + registerTypeSyntax( + Type::MATERIAL, + std::make_shared( + this, + "MATERIAL", + "null_closure()", + "0", + "closure color", + "#define MATERIAL closure color")); +} + +const string& OslNodesSyntax::getOutputQualifier() const +{ + return OUTPUT_QUALIFIER; +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXGenOslNodes/OslNodesSyntax.h b/source/MaterialXGenOslNodes/OslNodesSyntax.h new file mode 100644 index 0000000000..f43ab7c80b --- /dev/null +++ b/source/MaterialXGenOslNodes/OslNodesSyntax.h @@ -0,0 +1,41 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_OSLNODESSYNTAX_H +#define MATERIALX_OSLNODESSYNTAX_H + +/// @file +/// OSL syntax class + +#include + +#include + +MATERIALX_NAMESPACE_BEGIN + +/// @class OslSyntax +/// Syntax class for OSL (Open Shading Language) +class MX_GENOSLNODES_API OslNodesSyntax : public Syntax +{ + public: + OslNodesSyntax(TypeSystemPtr typeSystem); + + static SyntaxPtr create(TypeSystemPtr typeSystem) { return std::make_shared(typeSystem); } + + const string& getOutputQualifier() const override; + const string& getConstantQualifier() const override { return EMPTY_STRING; }; + const string& getSourceFileExtension() const override { return SOURCE_FILE_EXTENSION; }; + + static const string OUTPUT_QUALIFIER; + static const string SOURCE_FILE_EXTENSION; + static const StringVec VECTOR_MEMBERS; + static const StringVec VECTOR2_MEMBERS; + static const StringVec VECTOR4_MEMBERS; + static const StringVec COLOR4_MEMBERS; +}; + +MATERIALX_NAMESPACE_END + +#endif diff --git a/source/MaterialXGenShader/GenOptions.h b/source/MaterialXGenShader/GenOptions.h index 5b77c5ca35..9501c25d4d 100644 --- a/source/MaterialXGenShader/GenOptions.h +++ b/source/MaterialXGenShader/GenOptions.h @@ -94,7 +94,9 @@ class MX_GENSHADER_API GenOptions hwNormalizeUdimTexCoords(false), hwWriteAlbedoTable(false), hwWriteEnvPrefilter(false), - hwImplicitBitangents(true) + hwImplicitBitangents(true), + oslImplicitSurfaceShaderConversion(true), + oslNodesConnectCiWrapper(false) { } virtual ~GenOptions() { } @@ -197,6 +199,15 @@ class MX_GENSHADER_API GenOptions /// Calculate fallback bitangents from existing normals and tangents /// inside the bitangent node. bool hwImplicitBitangents; + + // Enables OSL conversion of surfaceshader struct to closure color. + // Defaults to true. + bool oslImplicitSurfaceShaderConversion; + + // Enables an OSL node that adds the root's output to the Ci variable + // for OSL Node targets. + // Defaults to false. + bool oslNodesConnectCiWrapper; }; MATERIALX_NAMESPACE_END diff --git a/source/MaterialXGenShader/ShaderNode.cpp b/source/MaterialXGenShader/ShaderNode.cpp index 73f56b17e6..fe13fbce1d 100644 --- a/source/MaterialXGenShader/ShaderNode.cpp +++ b/source/MaterialXGenShader/ShaderNode.cpp @@ -161,16 +161,22 @@ const string ShaderNode::GEOMETRIC_GROUPNAME = "geometric"; // ShaderNode::ShaderNode(const ShaderGraph* parent, const string& name) : + ShaderNode(parent, name, "") +{ +} + +ShaderNode::ShaderNode(const ShaderGraph* parent, const string& name, const string& nodeDefName) : _parent(parent), _name(name), _classification(0), - _impl(nullptr) + _impl(nullptr), + _nodeDefName(nodeDefName) { } ShaderNodePtr ShaderNode::create(const ShaderGraph* parent, const string& name, const NodeDef& nodeDef, GenContext& context) { - ShaderNodePtr newNode = std::make_shared(parent, name); + ShaderNodePtr newNode = std::make_shared(parent, name, nodeDef.getName()); const ShaderGenerator& shadergen = context.getShaderGenerator(); @@ -199,14 +205,14 @@ ShaderNodePtr ShaderNode::create(const ShaderGraph* parent, const string& name, if (context.getShaderGenerator().getSyntax().remapEnumeration(portValue, portType, enumNames, enumResult)) { input = newNode->addInput(port->getName(), enumResult.first); - input->setValue(enumResult.second); + input->setValue(enumResult.second, true); } else { input = newNode->addInput(port->getName(), portType); if (!portValue.empty()) { - input->setValue(port->getResolvedValue()); + input->setValue(port->getResolvedValue(), true); } } if (port->getIsUniform()) diff --git a/source/MaterialXGenShader/ShaderNode.h b/source/MaterialXGenShader/ShaderNode.h index 527c588fc7..71167c0bda 100644 --- a/source/MaterialXGenShader/ShaderNode.h +++ b/source/MaterialXGenShader/ShaderNode.h @@ -165,11 +165,14 @@ class MX_GENSHADER_API ShaderPort : public std::enable_shared_from_this&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); + string sceneTemplateString; + sceneTemplateString.assign(std::istreambuf_iterator(sceneTemplateStream), + std::istreambuf_iterator()); + + // Perform token replacement + const string ENVIRONMENT_SHADER_PARAMETER_OVERRIDES("%environment_shader_parameter_overrides%"); + const string BACKGROUND_COLOR_STRING("%background_color%"); + const string OSL_COMMANDS("%oslCmd%"); + + StringMap replacementMap; + + string envOverrideString; + for (const auto& param : _envOslShaderParameterOverrides) + { + envOverrideString.append(param); + } + replacementMap[ENVIRONMENT_SHADER_PARAMETER_OVERRIDES] = envOverrideString; + replacementMap[OSL_COMMANDS] = _oslCmdStr; + replacementMap[BACKGROUND_COLOR_STRING] = std::to_string(DEFAULT_SCREEN_COLOR_LIN_REC709[0]) + " " + + std::to_string(DEFAULT_SCREEN_COLOR_LIN_REC709[1]) + " " + + std::to_string(DEFAULT_SCREEN_COLOR_LIN_REC709[2]); + string sceneString = replaceSubstrings(sceneTemplateString, replacementMap); + if ((sceneString == sceneTemplateString) || sceneTemplateString.empty()) + { + throw ExceptionRenderError("Scene template file: " + _oslTestRenderSceneTemplateFile.asString() + + " does not include proper tokens for rendering"); + } + + // Set the working directory for rendering. + FileSearchPath searchPath = getDefaultDataSearchPath(); + FilePath rootPath = searchPath.isEmpty() ? FilePath() : searchPath[0]; + FilePath origWorkingPath = FilePath::getCurrentPath(); + rootPath.setCurrentPath(); + + // Write scene file + const string sceneFileName("scene_template_oslcmd.xml"); + std::ofstream shaderFileStream; + shaderFileStream.open(sceneFileName); + + if (shaderFileStream.is_open()) + { + shaderFileStream << sceneString; + shaderFileStream.close(); + } + + // Set oso file paths + string osoPaths(_oslUtilityOSOPath); + osoPaths += PATH_LIST_SEPARATOR + _dataLibraryOSOPath.asString(); + osoPaths += PATH_LIST_SEPARATOR + dirPath.asString(); + osoPaths += PATH_LIST_SEPARATOR + dirPath.getParentPath().asString(); + + // Build and run render command + string command(_oslTestRenderExecutable); + command += " " + sceneFileName; + command += " " + outputFileName; + command += " -r " + std::to_string(_width) + " " + std::to_string(_height); + command += " --path " + osoPaths; + command += " -aa " + std::to_string(_raysPerPixelLit); + command += " > " + errorFile + redirectString; + + // Repeat the render command to allow for sporadic errors. + int returnValue = 0; + for (int i = 0; i < 5; i++) + { + returnValue = std::system(command.c_str()); + if (!returnValue) + { + break; + } + } + + // Restore the working directory after rendering. + origWorkingPath.setCurrentPath(); + + // Report errors on a non-zero return value. + if (returnValue) + { + std::ifstream errorStream(errorFile); + StringVec result; + string line; + unsigned int errCount = 0; + while (std::getline(errorStream, line)) + { + if (errCount++ > 10) + { + break; + } + result.push_back(line); + } + + StringVec errors; + errors.push_back("Errors reported in renderOSL:"); + for (size_t i = 0; i < result.size(); i++) + { + errors.push_back(result[i]); + } + errors.push_back("Command string: " + command); + errors.push_back("Command return code: " + std::to_string(returnValue)); + throw ExceptionRenderError("OSL rendering error", errors); + } +} + void OslRenderer::shadeOSL(const FilePath& dirPath, const string& shaderName, const string& outputName) { // If no command and include path specified then skip checking. @@ -381,20 +508,27 @@ void OslRenderer::render() _oslOutputFileName.assign(EMPTY_STRING); - // Use testshade - if (!_useTestRender) + if (_useOSLCmdStr) { - shadeOSL(_oslOutputFilePath, _oslShaderName, _oslShaderOutputName); + renderOSLNodes(_oslOutputFilePath, _oslShaderName); } - - // Use testrender else { - if (_oslShaderName.empty()) + // Use testshade + if (!_useTestRender) { - throw ExceptionRenderError("OSL shader name has not been specified"); + shadeOSL(_oslOutputFilePath, _oslShaderName, _oslShaderOutputName); + } + + // Use testrender + else + { + if (_oslShaderName.empty()) + { + throw ExceptionRenderError("OSL shader name has not been specified"); + } + renderOSL(_oslOutputFilePath, _oslShaderName, _oslShaderOutputName); } - renderOSL(_oslOutputFilePath, _oslShaderName, _oslShaderOutputName); } } diff --git a/source/MaterialXRenderOsl/OslRenderer.h b/source/MaterialXRenderOsl/OslRenderer.h index 912dc9e0e3..b1bd8a324f 100644 --- a/source/MaterialXRenderOsl/OslRenderer.h +++ b/source/MaterialXRenderOsl/OslRenderer.h @@ -187,6 +187,10 @@ class MX_RENDEROSL_API OslRenderer : public ShaderRenderer { _oslUtilityOSOPath = dirPath; } + void setDataLibraryOSOPath(const FilePath& dirPath) + { + _dataLibraryOSOPath = dirPath; + } /// Used to toggle to either use testrender or testshade during render validation /// By default testshade is used. @@ -196,6 +200,12 @@ class MX_RENDEROSL_API OslRenderer : public ShaderRenderer _useTestRender = useTestRender; } + /// Used to switch between testing oso files and osl command strings + void useOslCommandString(bool useOslCmdstr) + { + _useOSLCmdStr = useOslCmdstr; + } + /// Set the number of rays per pixel to be used for lit surfaces. void setRaysPerPixelLit(int rays) { @@ -208,6 +218,11 @@ class MX_RENDEROSL_API OslRenderer : public ShaderRenderer _raysPerPixelUnlit = rays; } + /// Set the osl command string that is to be tested + void setOSLCmdStr(const string& oslCmd) + { + _oslCmdStr = oslCmd; + } /// /// Compile OSL code stored in a file. Will throw an exception if an error occurs. /// @param oslFilePath OSL file path. @@ -230,6 +245,11 @@ class MX_RENDEROSL_API OslRenderer : public ShaderRenderer /// @param outputName Name of OSL shader output to use. void renderOSL(const FilePath& dirPath, const string& shaderName, const string& outputName); + /// Render using OSL command string. Will throw an exception if an error occurs. + /// @param dirPath Path to location containing input .oso file. + /// @param shaderName Name of OSL shader. A corresponding .oso file is assumed to exist in the output path folder. + void renderOSLNodes(const FilePath& dirPath, const string& shaderName); + /// Constructor OslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType); @@ -248,9 +268,12 @@ class MX_RENDEROSL_API OslRenderer : public ShaderRenderer string _oslShaderOutputName; string _oslShaderOutputType; FilePath _oslUtilityOSOPath; + FilePath _dataLibraryOSOPath; bool _useTestRender; + bool _useOSLCmdStr; int _raysPerPixelLit; int _raysPerPixelUnlit; + string _oslCmdStr; }; MATERIALX_NAMESPACE_END diff --git a/source/MaterialXTest/CMakeLists.txt b/source/MaterialXTest/CMakeLists.txt index ef0c2d585b..2ccbb06047 100644 --- a/source/MaterialXTest/CMakeLists.txt +++ b/source/MaterialXTest/CMakeLists.txt @@ -51,6 +51,10 @@ if(MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MD if(MATERIALX_BUILD_GEN_OSL) add_subdirectory(MaterialXGenOsl) target_link_libraries(MaterialXTest MaterialXGenOsl) + + if(MATERIALX_BUILD_GEN_OSL_NODES) + target_link_libraries(MaterialXTest MaterialXGenOslNodes) + endif() endif() if(MATERIALX_BUILD_GEN_MDL) add_subdirectory(MaterialXGenMdl) diff --git a/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp b/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp index 144b5435bb..5bf72a98a9 100644 --- a/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp +++ b/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp @@ -14,6 +14,7 @@ #endif #include +#include #include @@ -76,16 +77,16 @@ class BitangentOsl : public mx::ShaderNodeImpl class OslShaderRenderTester : public RenderUtil::ShaderRenderTester { public: - explicit OslShaderRenderTester(mx::ShaderGeneratorPtr shaderGenerator) : + explicit OslShaderRenderTester(mx::ShaderGeneratorPtr shaderGenerator, bool useOslCmdStr) : RenderUtil::ShaderRenderTester(shaderGenerator) { - // Preprocess to resolve to absolute image file names + // Preprocess to resolve to absolute image file names // and all non-POSIX separators must be converted to POSIX ones (this only affects running on Windows) _resolveImageFilenames = true; _customFilenameResolver = mx::StringResolver::create(); _customFilenameResolver->setFilenameSubstitution("\\\\", "/"); _customFilenameResolver->setFilenameSubstitution("\\", "/"); - + _useOslCmdStr = useOslCmdStr; } protected: @@ -106,6 +107,12 @@ class OslShaderRenderTester : public RenderUtil::ShaderRenderTester { _skipFiles.insert("standard_surface_onyx_hextiled.mtlx"); _skipFiles.insert("hextiled.mtlx"); + _skipFiles.insert("filename_cm_test.mtlx"); + _skipFiles.insert("shader_ops.mtlx"); + _skipFiles.insert("chiang_hair_surfaceshader.mtlx"); + _skipFiles.insert("network_surfaceshader.mtlx"); + _skipFiles.insert("sheen.mtlx"); + _skipFiles.insert("toon_shade.mtlx"); } bool saveImage(const mx::FilePath& filePath, mx::ConstImagePtr image, bool /*verticalFlip*/) const override @@ -115,6 +122,7 @@ class OslShaderRenderTester : public RenderUtil::ShaderRenderTester mx::ImageLoaderPtr _imageLoader; mx::OslRendererPtr _renderer; + bool _useOslCmdStr; }; // Renderer setup @@ -216,6 +224,7 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, mx::GenOptions& contextOptions = context.getOptions(); contextOptions = options; contextOptions.targetColorSpaceOverride = "lin_rec709"; + contextOptions.oslNodesConnectCiWrapper = true; // Apply local overrides for shader generation. shadergen.registerImplementation("IM_tangent_vector3_" + mx::OslShaderGenerator::TARGET, TangentOsl::create); @@ -236,6 +245,8 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, } CHECK(shader->getSourceCode().length() > 0); + std::string oslCmdStr = shader->getSourceCode(); + std::string shaderPath; mx::FilePath outputFilePath = outputPath; // Use separate directory for reduced output @@ -257,7 +268,7 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, { mx::ScopedTimer ioTimer(&profileTimes.languageTimes.ioTime); std::ofstream file; - file.open(shaderPath + ".osl"); + file.open(shaderPath + (_useOslCmdStr ? ".oslcmd" : ".osl")); file << shader->getSourceCode(); file.close(); } @@ -273,6 +284,7 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, _renderer->setRaysPerPixelUnlit(testOptions.enableReferenceQuality ? 8 : 1); // Validate compilation + if (!_useOslCmdStr) { mx::ScopedTimer compileTimer(&profileTimes.languageTimes.compileTime); _renderer->createProgram(shader); @@ -286,7 +298,7 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, mx::StringVec envOverrides; std::string envmap_filename("string envmap_filename \""); envmap_filename += testOptions.radianceIBLPath; - envmap_filename += "\";\n"; + envmap_filename += "\";\n"; envOverrides.push_back(envmap_filename); _renderer->setEnvShaderParameterOverrides(envOverrides); @@ -299,11 +311,21 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, const std::string& outputName = output->getVariable(); const std::string& outputType = typeSyntax.getTypeAlias().empty() ? typeSyntax.getName() : typeSyntax.getTypeAlias(); - const std::string& sceneTemplateFile = "scene_template.xml"; + const std::string& sceneTemplateFile = (_useOslCmdStr ? "scene_template_oslcmd.xml": "scene_template.xml"); // Set shader output name and type to use _renderer->setOslShaderOutput(outputName, outputType); + // Get oslcmdstr + _renderer->setOSLCmdStr(oslCmdStr); + _renderer->useOslCommandString(_useOslCmdStr); + // Osl Nodes Shaders record the oso path in an attribute + auto osoPathAttr = shader->getAttribute("osoPath"); + if (osoPathAttr) + { + _renderer->setDataLibraryOSOPath( osoPathAttr->getValueString() ); + } + // Set scene template file. For now we only have the constant color scene file mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::FilePath sceneTemplatePath = searchPath.find("resources/Utilities/"); @@ -365,6 +387,22 @@ TEST_CASE("Render: OSL TestSuite", "[renderosl]") mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); mx::FilePath optionsFilePath = searchPath.find("resources/Materials/TestSuite/_options.mtlx"); - OslShaderRenderTester renderTester(mx::OslShaderGenerator::create()); + OslShaderRenderTester renderTester(mx::OslShaderGenerator::create(), false); + renderTester.validate(optionsFilePath); +} + +TEST_CASE("Render: OSL Nodes TestSuite", "[renderoslnodes]") +{ + if (std::string(MATERIALX_OSL_BINARY_OSLC).empty() && + std::string(MATERIALX_OSL_BINARY_TESTRENDER).empty()) + { + INFO("Skipping the OSL test suite as its executable locations haven't been set."); + return; + } + + mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); + mx::FilePath optionsFilePath = searchPath.find("resources/Materials/TestSuite/_options.mtlx"); + + OslShaderRenderTester renderTester(mx::OslNodesShaderGenerator::create(), true); renderTester.validate(optionsFilePath); } diff --git a/source/MaterialXTest/MaterialXRenderOsl/Utilities/scene_template_oslcmd.xml b/source/MaterialXTest/MaterialXRenderOsl/Utilities/scene_template_oslcmd.xml new file mode 100644 index 0000000000..47362bd5de --- /dev/null +++ b/source/MaterialXTest/MaterialXRenderOsl/Utilities/scene_template_oslcmd.xml @@ -0,0 +1,32 @@ + + + + + + + %environment_shader_parameter_overrides% + shader envmap layer1; + + + + + + color Cin %background_color%; + %environment_shader_parameter_overrides% + shader raytype_background layer1; + + + + + + %oslCmd% + + + + diff --git a/source/MaterialXTest/MaterialXRenderOsl/Utilities/test_node.osl b/source/MaterialXTest/MaterialXRenderOsl/Utilities/test_node.osl new file mode 100644 index 0000000000..7d62703481 --- /dev/null +++ b/source/MaterialXTest/MaterialXRenderOsl/Utilities/test_node.osl @@ -0,0 +1,8 @@ +surface + +test_node( + color test_color = color(1, 0, 0), + output closure color Out_Ci = 0 +){ + Out_Ci = test_color * diffuse(N); +} \ No newline at end of file diff --git a/source/PyMaterialX/CMakeLists.txt b/source/PyMaterialX/CMakeLists.txt index 84c94dffa1..5c0adab4b8 100644 --- a/source/PyMaterialX/CMakeLists.txt +++ b/source/PyMaterialX/CMakeLists.txt @@ -37,7 +37,7 @@ endif() add_subdirectory(PyMaterialXCore) add_subdirectory(PyMaterialXFormat) -if (MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_MDL OR MATERIALX_BUILD_GEN_MSL) +if (MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_OSL_NODES OR MATERIALX_BUILD_GEN_MDL OR MATERIALX_BUILD_GEN_MSL) add_subdirectory(PyMaterialXGenShader) if (MATERIALX_BUILD_GEN_GLSL) add_subdirectory(PyMaterialXGenGlsl) @@ -48,6 +48,9 @@ if (MATERIALX_BUILD_GEN_GLSL OR MATERIALX_BUILD_GEN_OSL OR MATERIALX_BUILD_GEN_M if (MATERIALX_BUILD_GEN_OSL) add_subdirectory(PyMaterialXGenOsl) endif() + if (MATERIALX_BUILD_GEN_OSL_NODES) + add_subdirectory(PyMaterialXGenOslNodes) + endif() if (MATERIALX_BUILD_GEN_MDL) add_subdirectory(PyMaterialXGenMdl) endif() diff --git a/source/PyMaterialX/PyMaterialXGenOslNodes/CMakeLists.txt b/source/PyMaterialX/PyMaterialXGenOslNodes/CMakeLists.txt new file mode 100644 index 0000000000..e385b65a1b --- /dev/null +++ b/source/PyMaterialX/PyMaterialXGenOslNodes/CMakeLists.txt @@ -0,0 +1,26 @@ +file(GLOB pymaterialxgenoslnodes_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB pymaterialxgenoslnodes_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h") + +pybind11_add_module(PyMaterialXGenOslNodes SHARED ${PYBIND11_MODULE_FLAGS} ${pymaterialxgenoslnodes_source} ${pymaterialxgenoslnodes_headers}) + +if(APPLE) + set_target_properties(PyMaterialXGenOslNodes PROPERTIES CXX_VISIBILITY_PRESET "default") +endif() + +set_target_properties( + PyMaterialXGenOslNodes + PROPERTIES + OUTPUT_NAME PyMaterialXGenOslNodes + COMPILE_FLAGS "${EXTERNAL_COMPILE_FLAGS}" + LINK_FLAGS "${EXTERNAL_LINK_FLAGS}" + INSTALL_RPATH "${MATERIALX_UP_TWO_RPATH}" + DEBUG_POSTFIX "${MATERIALX_PYTHON_DEBUG_POSTFIX}") + +target_link_libraries( + PyMaterialXGenOslNodes + PUBLIC PyMaterialXGenShader + MaterialXGenOslNodes + PRIVATE ${CMAKE_DL_LIBS}) + +install(TARGETS PyMaterialXGenOslNodes + DESTINATION "${MATERIALX_PYTHON_FOLDER_NAME}") diff --git a/source/PyMaterialX/PyMaterialXGenOslNodes/PyModule.cpp b/source/PyMaterialX/PyMaterialXGenOslNodes/PyModule.cpp new file mode 100644 index 0000000000..537f4b2cba --- /dev/null +++ b/source/PyMaterialX/PyMaterialXGenOslNodes/PyModule.cpp @@ -0,0 +1,20 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +namespace py = pybind11; + +void bindPyOslNodesShaderGenerator(py::module& mod); + +PYBIND11_MODULE(PyMaterialXGenOslNodes, mod) +{ + mod.doc() = "Shader generation using Open Shading Language."; + + // PyMaterialXGenOsl depends on types defined in PyMaterialXGenShader + PYMATERIALX_IMPORT_MODULE(PyMaterialXGenShader); + + bindPyOslNodesShaderGenerator(mod); +} diff --git a/source/PyMaterialX/PyMaterialXGenOslNodes/PyOslNodesShaderGenerator.cpp b/source/PyMaterialX/PyMaterialXGenOslNodes/PyOslNodesShaderGenerator.cpp new file mode 100644 index 0000000000..5c1a35f35a --- /dev/null +++ b/source/PyMaterialX/PyMaterialXGenOslNodes/PyOslNodesShaderGenerator.cpp @@ -0,0 +1,34 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include +#include +#include + +namespace py = pybind11; +namespace mx = MaterialX; + +namespace +{ + // Creator wrapper to avoid having to expose the TypeSystem class in python + mx::ShaderGeneratorPtr OslNodesShaderGenerator_create() + { + return mx::OslNodesShaderGenerator::create(); + } +} + +void bindPyOslNodesShaderGenerator(py::module& mod) +{ + mod.attr("OSL_UNIFORMS") = mx::OSLNodes::UNIFORMS; + mod.attr("OSL_INPUTS") = mx::OSLNodes::INPUTS; + mod.attr("OSL_OUTPUTS") = mx::OSLNodes::OUTPUTS; + + py::class_(mod, "OslNodesShaderGenerator") + .def_static("create", &OslNodesShaderGenerator_create) + .def("getTarget", &mx::OslNodesShaderGenerator::getTarget) + .def("generate", &mx::OslNodesShaderGenerator::generate); +}