From 9d3aef7c046b74ae51b52b50f9f7b82c111efc95 Mon Sep 17 00:00:00 2001 From: Mriganka Arya Date: Fri, 26 Sep 2025 10:56:59 -0700 Subject: [PATCH] This commit adds testing functionality for osl command strings. Currently the string is overridden with a dummy value in RenderOsl.cpp --- source/MaterialXRenderOsl/OslRenderer.cpp | 149 +++++++++++++++++- source/MaterialXRenderOsl/OslRenderer.h | 19 +++ .../MaterialXRenderOsl/RenderOsl.cpp | 49 ++++-- .../Utilities/scene_template_oslcmd.xml | 32 ++++ .../Utilities/test_node.osl | 8 + 5 files changed, 240 insertions(+), 17 deletions(-) create mode 100644 source/MaterialXTest/MaterialXRenderOsl/Utilities/scene_template_oslcmd.xml create mode 100644 source/MaterialXTest/MaterialXRenderOsl/Utilities/test_node.osl diff --git a/source/MaterialXRenderOsl/OslRenderer.cpp b/source/MaterialXRenderOsl/OslRenderer.cpp index bcddb08123..8011cc1605 100644 --- a/source/MaterialXRenderOsl/OslRenderer.cpp +++ b/source/MaterialXRenderOsl/OslRenderer.cpp @@ -211,6 +211,132 @@ void OslRenderer::renderOSL(const FilePath& dirPath, const string& shaderName, c } } +void OslRenderer::renderOSLNodes(const FilePath& dirPath, const string& shaderName, const string& outputName) +{ + // If command options missing, skip testing. + if (_oslTestRenderExecutable.isEmpty() || + _oslTestRenderSceneTemplateFile.isEmpty() || _oslUtilityOSOPath.isEmpty()) + { + throw ExceptionRenderError("Command input arguments are missing"); + } + + // Determine the shader path from output path and shader name + FilePath shaderFilePath(dirPath); + shaderFilePath = shaderFilePath / shaderName; + string shaderPath = shaderFilePath.asString(); + + // Set output image name. + string outputFileName = shaderPath + "_oslcmd.png"; + _oslOutputFileName = outputFileName; + + // Use a known error file name to check + string errorFile(shaderPath + "_render_errors_oslcmd.txt"); + const string redirectString(" 2>&1"); + + // Read in scene template and replace the applicable tokens to have a valid ShaderGroup. + // Write to local file to use as input for rendering. + std::ifstream sceneTemplateStream(_oslTestRenderSceneTemplateFile); + 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 + 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 +507,27 @@ void OslRenderer::render() _oslOutputFileName.assign(EMPTY_STRING); - // Use testshade - if (!_useTestRender) + if (_useOSLCmdStr) { - shadeOSL(_oslOutputFilePath, _oslShaderName, _oslShaderOutputName); + renderOSLNodes(_oslOutputFilePath, _oslShaderName, _oslShaderOutputName); } - - // 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..2f8ee630cf 100644 --- a/source/MaterialXRenderOsl/OslRenderer.h +++ b/source/MaterialXRenderOsl/OslRenderer.h @@ -196,6 +196,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 +214,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 +241,12 @@ 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. + /// @param outputName Name of OSL shader output to use. + void renderOSLNodes(const FilePath& dirPath, const string& shaderName, const string& outputName); + /// Constructor OslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType); @@ -249,8 +266,10 @@ class MX_RENDEROSL_API OslRenderer : public ShaderRenderer string _oslShaderOutputType; FilePath _oslUtilityOSOPath; bool _useTestRender; + bool _useOSLCmdStr; int _raysPerPixelLit; int _raysPerPixelUnlit; + string _oslCmdStr; }; MATERIALX_NAMESPACE_END diff --git a/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp b/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp index 144b5435bb..9179b13754 100644 --- a/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp +++ b/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp @@ -76,7 +76,7 @@ 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 @@ -85,7 +85,7 @@ class OslShaderRenderTester : public RenderUtil::ShaderRenderTester _customFilenameResolver = mx::StringResolver::create(); _customFilenameResolver->setFilenameSubstitution("\\\\", "/"); _customFilenameResolver->setFilenameSubstitution("\\", "/"); - + _useOslCmdStr = useOslCmdStr; } protected: @@ -115,6 +115,7 @@ class OslShaderRenderTester : public RenderUtil::ShaderRenderTester mx::ImageLoaderPtr _imageLoader; mx::OslRendererPtr _renderer; + bool _useOslCmdStr; }; // Renderer setup @@ -235,6 +236,13 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, return false; } CHECK(shader->getSourceCode().length() > 0); + + std::string oslCmdStr = shader->getSourceCode(); + /// TODO: this is a temp value. + oslCmdStr = "shader test_node test_node;\n"; + oslCmdStr += "shader closure_passthrough closure_passthrough;\n"; + oslCmdStr += "connect test_node.Out_Ci closure_passthrough.Cin;\n"; + std::string shaderPath; mx::FilePath outputFilePath = outputPath; @@ -257,7 +265,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,10 +281,11 @@ bool OslShaderRenderTester::runRenderer(const std::string& shaderName, _renderer->setRaysPerPixelUnlit(testOptions.enableReferenceQuality ? 8 : 1); // Validate compilation - { - mx::ScopedTimer compileTimer(&profileTimes.languageTimes.compileTime); - _renderer->createProgram(shader); - } + if (!_useOslCmdStr) + { + mx::ScopedTimer compileTimer(&profileTimes.languageTimes.compileTime); + _renderer->createProgram(shader); + } _renderer->setSize(static_cast(testOptions.renderSize[0]), static_cast(testOptions.renderSize[1])); @@ -299,10 +308,15 @@ 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); + // Set scene template file. For now we only have the constant color scene file mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath(); @@ -365,6 +379,23 @@ 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", "[oslNodes]") +{ + 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"); + + /// TODO: change to chris' new shader generator + OslShaderRenderTester renderTester(mx::OslShaderGenerator::create(), true); + renderTester.validate(optionsFilePath); +} \ No newline at end of file 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