From a1be9fa225a37619eaa9b2fbbe94b8cff0dd2f59 Mon Sep 17 00:00:00 2001 From: Joshua Senouf Date: Thu, 25 Sep 2025 21:11:49 -0400 Subject: [PATCH 1/3] Introduced a WIP standalone binary tool, "LibsToOso", allowing us to codegen and compile MaterialX libraries, as part as the ongoing work towards a new shader generator, "MaterialXGenOslNodes". --- CMakeLists.txt | 11 +- source/MaterialXGenOslNodes/CMakeLists.txt | 29 ++ source/MaterialXGenOslNodes/LibsToOso.cpp | 319 +++++++++++++++++++++ 3 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 source/MaterialXGenOslNodes/CMakeLists.txt create mode 100644 source/MaterialXGenOslNodes/LibsToOso.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 63ae3ab53c..8bf4ed8a6d 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() @@ -162,6 +164,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 +218,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) @@ -449,7 +452,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 +461,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/source/MaterialXGenOslNodes/CMakeLists.txt b/source/MaterialXGenOslNodes/CMakeLists.txt new file mode 100644 index 0000000000..64a333d31f --- /dev/null +++ b/source/MaterialXGenOslNodes/CMakeLists.txt @@ -0,0 +1,29 @@ + + +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`... +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/LibsToOso.cpp b/source/MaterialXGenOslNodes/LibsToOso.cpp new file mode 100644 index 0000000000..53a22f361e --- /dev/null +++ b/source/MaterialXGenOslNodes/LibsToOso.cpp @@ -0,0 +1,319 @@ +// +// 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 options = + " Options: \n" + " --outputPath [DIRPATH] TODO\n" + " --oslCompilerPath [FILEPATH] TODO\n" + " --oslIncludePath [DIRPATH] TODO\n" + " --libraries [STRING] 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 argOutputPath; + std::string argOslCompilerPath; + std::string argOslIncludePath; + std::string argLibraries; + 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 == "--outputPath") + { + argOutputPath = nextToken; + } + else if (token == "--oslCompilerPath") + { + argOslCompilerPath = nextToken; + } + else if (token == "--oslIncludePath") + { + argOslIncludePath = nextToken; + } + else if (token == "--libraries") + { + argLibraries = nextToken; + } + 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++; + } + } + + // TODO: Debug prints, to be removed. + std::cout << "MaterialXGenOslNodes - LibsToOso" << std::endl; + std::cout << "\toutputPath: " << argOutputPath << std::endl; + std::cout << "\toslCompilerPath: " << argOslCompilerPath << std::endl; + std::cout << "\toslIncludePath: " << argOslIncludePath << std::endl; + std::cout << "\tlibraries: " << argLibraries << std::endl; + std::cout << "\tprefix: " << argPrefix << std::endl; + + // Ensure we have a valid output path. + mx::FilePath outputPath(argOutputPath); + + if (!outputPath.exists() || !outputPath.isDirectory()) + { + outputPath.createDirectory(); + + if (!outputPath.exists() || !outputPath.isDirectory()) + { + std::cerr << "Failed to find and/or create the provided output " + "path: " + << outputPath.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); + } + + // 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.getOptions().addUpstreamDependencies = false; + context.registerSourceCodeSearchPath(librariesSearchPath); + context.getOptions().fileTextureVerticalFlip = true; + + // TODO: Add control over the name of the log file? + // Create a log file in the provided output path. + const mx::FilePath& logFilePath(outputPath.asString() + "/genoslnodes_libs_to_oso.txt"); + std::ofstream logFile; + + logFile.open(logFilePath); + + // We'll use this boolean to return an error code is one of the `NodeDef` failed to codegen/compile. + bool hasFailed = false; + + // 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 (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) + { + logFile << "The following `NodeDef` does not provide a valid OSL implementation, " + "and will be skipped: " + << nodeName << std::endl; + + continue; + } + + // TODO: Check for the existence/validity of the `Node`? + mx::NodePtr node = librariesDoc->addNodeInstance(nodeDef, nodeName); + const std::string oslFileName = nodeName + ".osl"; + + try + { + // Codegen the `Node` to OSL. + mx::ShaderPtr oslShader = oslShaderGen->generate(node->getName(), node, context); + + const std::string& oslFilePath = (outputPath / oslFileName).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 << oslShader->getSourceCode(); + 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) + { + logFile << "Encountered a codegen/compilation related exception for the " + "following node: " + << nodeName << std::endl; + logFile << exc.what() << std::endl; + + // Dump details about the exception in the log file. + for (const std::string& error : exc.errorLog()) + { + logFile << error << std::endl; + } + + hasFailed = true; + } + // Catch any other exceptions + catch (mx::Exception& exc) + { + logFile << "Failed to codegen/compile the following node to OSL: " << nodeName << std::endl; + logFile << exc.what() << std::endl; + + hasFailed = true; + } + + librariesDoc->removeChild(node->getName()); + } + + logFile.close(); + + // 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; +} From 7fb308d7178601f263e0b43140029c5b3027d5c9 Mon Sep 17 00:00:00 2001 From: Joshua Senouf Date: Sun, 28 Sep 2025 14:27:04 -0400 Subject: [PATCH 2/3] Addressed PR comments. --- CMakeLists.txt | 7 ++ source/MaterialXGenOslNodes/CMakeLists.txt | 5 +- source/MaterialXGenOslNodes/LibsToOso.cpp | 106 +++++++++------------ 3 files changed, 53 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bf4ed8a6d..c3bff685c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,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() @@ -138,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.") diff --git a/source/MaterialXGenOslNodes/CMakeLists.txt b/source/MaterialXGenOslNodes/CMakeLists.txt index 64a333d31f..0999bdda16 100644 --- a/source/MaterialXGenOslNodes/CMakeLists.txt +++ b/source/MaterialXGenOslNodes/CMakeLists.txt @@ -1,5 +1,3 @@ - - file(GLOB GenNodes_SRC "${CMAKE_CURRENT_SOURCE_DIR}/LibsToOso.cpp") set(MATERIALX_LIBRARIES @@ -19,7 +17,8 @@ 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`... +# 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}) diff --git a/source/MaterialXGenOslNodes/LibsToOso.cpp b/source/MaterialXGenOslNodes/LibsToOso.cpp index 53a22f361e..1284c35c68 100644 --- a/source/MaterialXGenOslNodes/LibsToOso.cpp +++ b/source/MaterialXGenOslNodes/LibsToOso.cpp @@ -27,15 +27,14 @@ const std::string options = " --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); @@ -63,6 +62,7 @@ int main(int argc, char* const argv[]) 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. @@ -72,25 +72,17 @@ int main(int argc, char* const argv[]) const std::string& nextToken = i + 1 < tokens.size() ? tokens[i + 1] : mx::EMPTY_STRING; if (token == "--outputPath") - { argOutputPath = 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; @@ -109,13 +101,9 @@ int main(int argc, char* const argv[]) } if (nextToken.empty()) - { std::cout << "Expected another token following command-line option: " << token << std::endl; - } else - { i++; - } } // TODO: Debug prints, to be removed. @@ -124,6 +112,7 @@ int main(int argc, char* const argv[]) std::cout << "\toslCompilerPath: " << argOslCompilerPath << std::endl; std::cout << "\toslIncludePath: " << argOslIncludePath << std::endl; std::cout << "\tlibraries: " << argLibraries << std::endl; + std::cout << "\tremoveNdPrefix: " << argRemoveNdPrefix << std::endl; std::cout << "\tprefix: " << argPrefix << std::endl; // Ensure we have a valid output path. @@ -175,17 +164,13 @@ int main(int argc, char* const argv[]) 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); - } // Create and setup the `OslRenderer` that will be used to both generate the `.osl` files as well as compile // them to `.oso` files. @@ -210,35 +195,31 @@ int main(int argc, char* const argv[]) // Setup the context of the OSL shader generator. mx::GenContext context(oslShaderGen); - context.getOptions().addUpstreamDependencies = false; 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 = true; - // TODO: Add control over the name of the log file? - // Create a log file in the provided output path. - const mx::FilePath& logFilePath(outputPath.asString() + "/genoslnodes_libs_to_oso.txt"); - std::ofstream logFile; - - logFile.open(logFilePath); - // We'll use this boolean to return an error code is one of the `NodeDef` failed to codegen/compile. bool hasFailed = false; + // 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 (nodeName.size() > 3 && nodeName.substr(0, 3) == "ND_") + if (argRemoveNdPrefix) { - nodeName = nodeName.substr(3); - } + 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; + // 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 @@ -247,64 +228,65 @@ int main(int argc, char* const argv[]) if (!nodeImpl) { - logFile << "The following `NodeDef` does not provide a valid OSL implementation, " - "and will be skipped: " - << nodeName << std::endl; + 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); - const std::string oslFileName = nodeName + ".osl"; + mx::NodePtr node = librariesDocGraph->addNodeInstance(nodeDef, nodeName); + + const std::string& oslFileName = nodeName + ".osl"; + const std::string& oslFilePath = (outputPath / 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); - const std::string& oslFilePath = (outputPath / oslFileName).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 << 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); } // Catch any codegen/compilation related exceptions. catch (mx::ExceptionRenderError& exc) { - logFile << "Encountered a codegen/compilation related exception for the " - "following node: " - << nodeName << std::endl; - logFile << exc.what() << std::endl; + 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()) - { - logFile << error << std::endl; - } - - hasFailed = true; - } - // Catch any other exceptions - catch (mx::Exception& exc) - { - logFile << "Failed to codegen/compile the following node to OSL: " << nodeName << std::endl; - logFile << exc.what() << std::endl; + std::cerr << error << std::endl; hasFailed = true; } - - librariesDoc->removeChild(node->getName()); } - logFile.close(); - // If something went wrong, return an appropriate error code. if (hasFailed) { From a37b8cf98947b8e75dbd7f8a06c2d20df84bb395 Mon Sep 17 00:00:00 2001 From: Joshua Senouf Date: Mon, 29 Sep 2025 18:55:09 -0400 Subject: [PATCH 3/3] Split the "outputPath" argument into "oslOutputPath" and "osoOutputPath", with the latter being optional, so we can split where we are codegening and compiling our shaders. --- source/MaterialXGenOslNodes/LibsToOso.cpp | 56 +++++++++++++++++------ 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/source/MaterialXGenOslNodes/LibsToOso.cpp b/source/MaterialXGenOslNodes/LibsToOso.cpp index 1284c35c68..ce542a8b0d 100644 --- a/source/MaterialXGenOslNodes/LibsToOso.cpp +++ b/source/MaterialXGenOslNodes/LibsToOso.cpp @@ -23,7 +23,8 @@ namespace mx = MaterialX; const std::string options = " Options: \n" - " --outputPath [DIRPATH] TODO\n" + " --oslOutputPath [DIRPATH] TODO\n" + " --osoOutputPath [DIRPATH] TODO\n" " --oslCompilerPath [FILEPATH] TODO\n" " --oslIncludePath [DIRPATH] TODO\n" " --libraries [STRING] TODO\n" @@ -58,7 +59,8 @@ int main(int argc, char* const argv[]) tokens.emplace_back(argv[i]); } - std::string argOutputPath; + std::string argOslOutputPath; + std::string argOsoOutputPath; std::string argOslCompilerPath; std::string argOslIncludePath; std::string argLibraries; @@ -71,8 +73,10 @@ int main(int argc, char* const argv[]) const std::string& token = tokens[i]; const std::string& nextToken = i + 1 < tokens.size() ? tokens[i + 1] : mx::EMPTY_STRING; - if (token == "--outputPath") - argOutputPath = nextToken; + if (token == "--oslOutputPath") + argOslOutputPath = nextToken; + else if (token == "--osoOutputPath") + argOsoOutputPath = nextToken; else if (token == "--oslCompilerPath") argOslCompilerPath = nextToken; else if (token == "--oslIncludePath") @@ -108,30 +112,55 @@ int main(int argc, char* const argv[]) // TODO: Debug prints, to be removed. std::cout << "MaterialXGenOslNodes - LibsToOso" << std::endl; - std::cout << "\toutputPath: " << argOutputPath << std::endl; + std::cout << "\toslOutputPath: " << argOslOutputPath << std::endl; + std::cout << "\tosoOutputPath: " << argOsoOutputPath << std::endl; std::cout << "\toslCompilerPath: " << argOslCompilerPath << std::endl; std::cout << "\toslIncludePath: " << argOslIncludePath << std::endl; std::cout << "\tlibraries: " << argLibraries << std::endl; std::cout << "\tremoveNdPrefix: " << argRemoveNdPrefix << std::endl; std::cout << "\tprefix: " << argPrefix << std::endl; - // Ensure we have a valid output path. - mx::FilePath outputPath(argOutputPath); + // Ensure we have a valid OSL output path. + mx::FilePath oslOutputPath(argOslOutputPath); - if (!outputPath.exists() || !outputPath.isDirectory()) + if (!oslOutputPath.exists() || !oslOutputPath.isDirectory()) { - outputPath.createDirectory(); + oslOutputPath.createDirectory(); - if (!outputPath.exists() || !outputPath.isDirectory()) + if (!oslOutputPath.exists() || !oslOutputPath.isDirectory()) { - std::cerr << "Failed to find and/or create the provided output " + std::cerr << "Failed to find and/or create the provided OSL output " "path: " - << outputPath.asString() << std::endl; + << oslOutputPath.asString() << std::endl; return 1; } } + // If provided, ensure we have a valid OSO output path, otherwise use the OSL output path. + mx::FilePath osoOutputPath(argOsoOutputPath); + + if (osoOutputPath.isEmpty()) + { + osoOutputPath = oslOutputPath; + } + else + { + if (!osoOutputPath.exists() || !osoOutputPath.isDirectory()) + { + osoOutputPath.createDirectory(); + + if (!osoOutputPath.exists() || !osoOutputPath.isDirectory()) + { + std::cerr << "Failed to find and/or create the provided OSO output " + "path: " + << osoOutputPath.asString() << std::endl; + + return 1; + } + } + } + // Ensure we have a valid path to the OSL compiler. mx::FilePath oslCompilerPath(argOslCompilerPath); @@ -176,6 +205,7 @@ int main(int argc, char* const argv[]) // them to `.oso` files. mx::OslRendererPtr oslRenderer = mx::OslRenderer::create(); oslRenderer->setOslCompilerExecutable(oslCompilerPath); + oslRenderer->setOslOutputFilePath(osoOutputPath); // Build the list of include paths that will be passed to the `OslRenderer`. mx::FileSearchPath oslRendererIncludePaths; @@ -239,7 +269,7 @@ int main(int argc, char* const argv[]) mx::NodePtr node = librariesDocGraph->addNodeInstance(nodeDef, nodeName); const std::string& oslFileName = nodeName + ".osl"; - const std::string& oslFilePath = (outputPath / oslFileName).asString(); + const std::string& oslFilePath = (oslOutputPath / oslFileName).asString(); std::ofstream oslFile; // Codegen the `Node` to an `.osl` file.