diff --git a/CMakeLists.txt b/CMakeLists.txt index 1465bfe403..d53a601aab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ 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) option(MATERIALX_BUILD_OIIO "Build OpenImageIO support for MaterialXRender." OFF) +option(MATERIALX_BUILD_OCIO "Build OpenColorIO support for shader generators." OFF) option(MATERIALX_BUILD_TESTS "Build unit tests." ON) option(MATERIALX_BUILD_BENCHMARK_TESTS "Build benchmark tests." OFF) @@ -135,6 +136,7 @@ mark_as_advanced(MATERIALX_BUILD_GEN_MDL) mark_as_advanced(MATERIALX_BUILD_GEN_MSL) mark_as_advanced(MATERIALX_BUILD_RENDER) mark_as_advanced(MATERIALX_BUILD_OIIO) +mark_as_advanced(MATERIALX_BUILD_OCIO) mark_as_advanced(MATERIALX_BUILD_TESTS) mark_as_advanced(MATERIALX_BUILD_BENCHMARK_TESTS) mark_as_advanced(MATERIALX_BUILD_SHARED_LIBS) diff --git a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp index f8ed1a4892..37126dd714 100644 --- a/source/MaterialXGenGlsl/GlslShaderGenerator.cpp +++ b/source/MaterialXGenGlsl/GlslShaderGenerator.cpp @@ -885,8 +885,12 @@ ShaderNodeImplPtr GlslShaderGenerator::getImplementation(const NodeDef& nodedef, } else if (implElement->isA()) { - // Try creating a new in the factory. - impl = _implFactory.create(name); + if (getColorManagementSystem() && getColorManagementSystem()->hasImplementation(name)) { + impl = getColorManagementSystem()->createImplementation(name); + } else { + // Try creating a new in the factory. + impl = _implFactory.create(name); + } if (!impl) { // Fall back to source code implementation. diff --git a/source/MaterialXGenShader/CMakeLists.txt b/source/MaterialXGenShader/CMakeLists.txt index 1b9533e0a5..de293c93ad 100644 --- a/source/MaterialXGenShader/CMakeLists.txt +++ b/source/MaterialXGenShader/CMakeLists.txt @@ -1,5 +1,9 @@ set(MATERIALX_MODULE_NAME MaterialXGenShader) +if(MATERIALX_BUILD_OCIO) + find_package(OpenColorIO REQUIRED) +endif() + file(GLOB_RECURSE materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") file(GLOB_RECURSE materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*") @@ -31,6 +35,13 @@ target_link_libraries( MaterialXFormat ${CMAKE_DL_LIBS}) +if(MATERIALX_BUILD_OCIO) + target_compile_definitions(${MATERIALX_MODULE_NAME} PUBLIC MATERIALX_BUILD_OCIO) + target_link_libraries( + ${MATERIALX_MODULE_NAME} + OpenColorIO::OpenColorIO) +endif() + target_include_directories(${MATERIALX_MODULE_NAME} PUBLIC $ diff --git a/source/MaterialXGenShader/ColorManagementSystem.h b/source/MaterialXGenShader/ColorManagementSystem.h index caf3826f94..f9f4dafb4a 100644 --- a/source/MaterialXGenShader/ColorManagementSystem.h +++ b/source/MaterialXGenShader/ColorManagementSystem.h @@ -64,6 +64,12 @@ class MX_GENSHADER_API ColorManagementSystem ShaderNodePtr createNode(const ShaderGraph* parent, const ColorSpaceTransform& transform, const string& name, GenContext& context) const; + /// Returns true if the CMS can create a shader node implementation for a locally managed CMS transform + virtual bool hasImplementation(const string& /*implName*/) const { return false; } + + /// Create an CMS node implementation for a locally managed transform + virtual ShaderNodeImplPtr createImplementation(const string& /*implName*/) const { return {}; } + protected: /// Protected constructor ColorManagementSystem(); diff --git a/source/MaterialXGenShader/Nodes/OpenColorIONode.cpp b/source/MaterialXGenShader/Nodes/OpenColorIONode.cpp new file mode 100644 index 0000000000..164dbf267c --- /dev/null +++ b/source/MaterialXGenShader/Nodes/OpenColorIONode.cpp @@ -0,0 +1,142 @@ +// +// Copyright Contributors to the MaterialXOCIO Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +MATERIALX_NAMESPACE_BEGIN + +namespace +{ +// Internal OCIO strings: +constexpr const char OCIO_COLOR3[] = "color3"; + +// Lengths where needed: +constexpr auto OCIO_COLOR3_LEN = sizeof(OCIO_COLOR3) / sizeof(OCIO_COLOR3[0]); + +} // namespace + +ShaderNodeImplPtr OpenColorIONode::create() +{ + return std::make_shared(); +} + +void OpenColorIONode::initialize(const InterfaceElement& element, GenContext& context) +{ + ShaderNodeImpl::initialize(element, context); + + // Single function shared between color3 and color4 nodes, use a custom hash with only the function name. + _hash = std::hash{}(getFunctionName()); +} + +void OpenColorIONode::emitFunctionDefinition( + const ShaderNode& /*node*/, + GenContext& context, + ShaderStage& stage) const +{ + if (stage.getName() == Stage::PIXEL) + { + auto ocioManager = std::dynamic_pointer_cast(context.getShaderGenerator().getColorManagementSystem()); + + auto gpuProcessor = ocioManager->getGpuProcessor(getName()); + OCIO::GpuShaderDescRcPtr shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc(); + + // TODO: Extend to essl and MDL and possibly SLang. + if (context.getShaderGenerator().getTarget() == "genglsl") { + shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_4_0); + } else if (context.getShaderGenerator().getTarget() == "genmsl") { + shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_MSL_2_0); + } else if (context.getShaderGenerator().getTarget() == "genosl") { + shaderDesc->setLanguage(OCIO::LANGUAGE_OSL_1); + } + + auto functionName = getFunctionName(); + + shaderDesc->setFunctionName(functionName.c_str()); + + gpuProcessor->extractGpuShaderInfo(shaderDesc); + + stage.addString(shaderDesc->getShaderText()); + stage.endLine(false); + } +} + +void OpenColorIONode::emitFunctionCall( + const ShaderNode& node, + GenContext& context, + ShaderStage& stage) const +{ + if (stage.getName() == Stage::PIXEL) + { + auto functionName = getFunctionName(); + + // TODO: Adjust syntax for other languages. OSL definitely does not have a native color4 type + // and requires an add-on struct to be used. + + // The OCIO function uses a vec4 parameter, so: + // Function call for color4: vec4 res = func(in); + // Function call for color3: vec3 res = func(vec4(in, 1.0)).rgb; + // TODO: Handle LUT samplers. + bool isColor3 = getName().back() == '3'; + + const auto& shadergen = context.getShaderGenerator(); + shadergen.emitLineBegin(stage); + + const auto* output = node.getOutput(); + const auto* colorInput = node.getInput(0); + + shadergen.emitOutput(output, true, false, context, stage); + shadergen.emitString(" = ", stage); + + shadergen.emitString(functionName + "(", stage); + if (isColor3) + { + shadergen.emitString("vec4(", stage); + } + shadergen.emitInput(colorInput, context, stage); + if (isColor3) + { + shadergen.emitString(", 1.0)", stage); + } + + shadergen.emitString(")", stage); + + if (isColor3) + { + shadergen.emitString(".rgb", stage); + } + + shadergen.emitLineEnd(stage); + } +} + +std::string OpenColorIONode::getFunctionName() const +{ + auto name = getName(); + + // Strip _color3 and _color4 suffixes and impl prefix: + size_t startPos = OpenColorIOManagementSystem::IMPL_PREFIX.size(); + size_t length = name.size() - OCIO_COLOR3_LEN - 1 - startPos; + + return name.substr(startPos, length); +} + +MATERIALX_NAMESPACE_END diff --git a/source/MaterialXGenShader/Nodes/OpenColorIONode.h b/source/MaterialXGenShader/Nodes/OpenColorIONode.h new file mode 100644 index 0000000000..b1b8139400 --- /dev/null +++ b/source/MaterialXGenShader/Nodes/OpenColorIONode.h @@ -0,0 +1,48 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_OCIO_NODE_H +#define MATERIALX_OCIO_NODE_H + +#ifdef MATERIALX_BUILD_OCIO +/// @file +/// OCIO node implementation + +#include +#include +#include +#include +#include +#include + +#include + +namespace mx = MaterialX; + +MATERIALX_NAMESPACE_BEGIN + +/// GLSL OCIO node implementation. Takes a Maya OCIO shader fragment and +/// makes it compatible with the shadergen +class OpenColorIONode : public ShaderNodeImpl +{ + public: + static ShaderNodeImplPtr create(); + + void initialize(const InterfaceElement& element, GenContext& context) override; + + void emitFunctionDefinition(const ShaderNode& node, GenContext& context, ShaderStage& stage) + const override; + + void emitFunctionCall(const ShaderNode& node, GenContext& context, ShaderStage& stage) + const override; + + private: + std::string getFunctionName() const; +}; + +MATERIALX_NAMESPACE_END + +#endif +#endif diff --git a/source/MaterialXGenShader/OpenColorIOManagementSystem.cpp b/source/MaterialXGenShader/OpenColorIOManagementSystem.cpp new file mode 100644 index 0000000000..e4d8bfa8bd --- /dev/null +++ b/source/MaterialXGenShader/OpenColorIOManagementSystem.cpp @@ -0,0 +1,179 @@ +// +// Copyright Contributors to the MaterialX Project +// SPDX-License-Identifier: Apache-2.0 +// + +#include "MaterialXCore/Library.h" +#ifdef MATERIALX_BUILD_OCIO + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +MATERIALX_NAMESPACE_BEGIN + +const std::string OpenColorIOManagementSystem::IMPL_PREFIX = "IMPL_MXOCIO_"; +const std::string OpenColorIOManagementSystem::ND_PREFIX = "ND_MXOCIO_"; + +namespace +{ + +const std::string CMS_NAME = "OpenColorIO"; + +// Remap from legacy color space names to their ACES 1.3 equivalents. +const std::map COLOR_SPACE_REMAP = { + { "gamma18", "Gamma 1.8 Rec.709 - Texture" }, + { "gamma22", "Gamma 2.2 Rec.709 - Texture" }, + { "gamma24", "Gamma 2.4 Rec.709 - Texture" }, + // TODO(Maybe): Add support for adobergb and lin_adobergb +}; + +} // anonymous namespace + +// +// OpenColorIOManagementSystem methods +// + +OpenColorIOManagementSystemPtr OpenColorIOManagementSystem::create(const OCIO::ConstConfigRcPtr& config, const std::string& target, bool useNativeGenerator) +{ + return OpenColorIOManagementSystemPtr(new OpenColorIOManagementSystem(config, target, useNativeGenerator)); +} + +OpenColorIOManagementSystem::OpenColorIOManagementSystem(const OCIO::ConstConfigRcPtr& config, const std::string& target, bool useNativeGenerator) : + _target(target), + _config(std::move(config)), + _useNativeGenerator(useNativeGenerator) +{ +} + +const std::string& OpenColorIOManagementSystem::getName() const +{ + return CMS_NAME; +} + +const char* OpenColorIOManagementSystem::getSupportedColorSpaceName(const char* colorSpace) const +{ + if (_config->getColorSpace(colorSpace)) + { + return colorSpace; + } + + auto remap = COLOR_SPACE_REMAP.find(colorSpace); + if (remap != COLOR_SPACE_REMAP.end()) + { + return getSupportedColorSpaceName(remap->second.c_str()); + } + + auto cgConfig = OCIO::Config::CreateFromBuiltinConfig("ocio://studio-config-latest"); + try + { + // Throws on failure. Try at least two configs: + return OCIO::Config::IdentifyBuiltinColorSpace(_config, cgConfig, colorSpace); + } + catch (const std::exception& /*e*/) + { + return nullptr; + } +} + +NodeDefPtr OpenColorIOManagementSystem::getNodeDef(const ColorSpaceTransform& transform) const +{ + OCIO::ConstProcessorRcPtr processor; + // Check if directly supported in the config: + const char* sourceColorSpace = getSupportedColorSpaceName(transform.sourceSpace.c_str()); + const char* targetColorSpace = getSupportedColorSpaceName(transform.targetSpace.c_str()); + if (!sourceColorSpace || !targetColorSpace) + { + return {}; + } + + try + { + processor = _config->getProcessor(sourceColorSpace, targetColorSpace); + } + catch (const std::exception& /*e*/) + { + return {}; + } + + if (!processor) + { + return {}; + } + + auto gpuProcessor = processor->getDefaultGPUProcessor(); + if (!gpuProcessor) + { + return {}; + } + + if (gpuProcessor->isNoOp()) + { + return _document->getNodeDef("ND_dot_" + transform.type->getName()); + } + + // Reject transforms requiring textures (1D and 3D LUTs) + OCIO::GpuShaderDescRcPtr shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc(); + gpuProcessor->extractGpuShaderInfo(shaderDesc); + if (shaderDesc->getNum3DTextures() || shaderDesc->getNumTextures()) + { + // TODO: Support LUTs + return {}; + } + + static const auto NODE_NAME = std::string{ "ocio_color_conversion" }; + const auto functionName = NODE_NAME + "_" + processor->getCacheID(); + const auto implName = IMPL_PREFIX + functionName + "_" + transform.type->getName(); + const auto nodeDefName = ND_PREFIX + functionName + "_" + transform.type->getName(); + auto nodeDef = _document->getNodeDef(nodeDefName); + if (!nodeDef) + { + nodeDef = _document->addNodeDef(nodeDefName, "", functionName); + nodeDef->setNodeGroup("colortransform"); + + nodeDef->addInput("in", transform.type->getName()); + nodeDef->addOutput("out", transform.type->getName()); + + auto implementation = _document->addImplementation(implName); + implementation->setTarget(_target); + implementation->setNodeDef(nodeDef); + } + + _implementations.emplace(implName, gpuProcessor); + + return nodeDef; +} + +bool OpenColorIOManagementSystem::hasImplementation(const string& implName) const +{ + return _implementations.count(implName); +} + +ShaderNodeImplPtr OpenColorIOManagementSystem::createImplementation(const string& implName) const +{ + if (_implementations.count(implName)) { + return OpenColorIONode::create(); + } + return {}; +} + +OCIO::ConstGPUProcessorRcPtr OpenColorIOManagementSystem::getGpuProcessor(const std::string& implName) +{ + auto it = _implementations.find(implName); + + return it != _implementations.end() ? it->second : nullptr; +} + +MATERIALX_NAMESPACE_END +#endif \ No newline at end of file diff --git a/source/MaterialXGenShader/OpenColorIOManagementSystem.h b/source/MaterialXGenShader/OpenColorIOManagementSystem.h new file mode 100644 index 0000000000..e214b94581 --- /dev/null +++ b/source/MaterialXGenShader/OpenColorIOManagementSystem.h @@ -0,0 +1,84 @@ +// +// Copyright Contributors to the Material Project +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef MATERIALX_OCIO_COLOR_MANAGEMENT_SYSTEM_H +#define MATERIALX_OCIO_COLOR_MANAGEMENT_SYSTEM_H + +#ifdef MATERIALX_BUILD_OCIO +/// @file +/// OCIO color management system implementation + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace OCIO = OCIO_NAMESPACE; + +MATERIALX_NAMESPACE_BEGIN + +/// A shared pointer to a OpenColorIOManagementSystem +using OpenColorIOManagementSystemPtr = std::shared_ptr; + +/// @class OpenColorIOManagementSystem +/// Class for a default color management system. +class MX_GENSHADER_API OpenColorIOManagementSystem : public ColorManagementSystem +{ + public: + virtual ~OpenColorIOManagementSystem() { } + + /// Create a new OpenColorIOManagementSystem + static OpenColorIOManagementSystemPtr create(const OCIO::ConstConfigRcPtr& config, const std::string& target, bool useNativeGenerator = true); + + /// Return the OpenColorIOManagementSystem name + const std::string& getName() const override; + + /// Can the CMS create a shader node implementation for one of its registered CMS transforms + bool hasImplementation(const string& implName) const override; + + /// Create an OCIO node + ShaderNodeImplPtr createImplementation(const string& implName) const override; + + /// Returns a cached GPU processor registered for an implementation + OCIO::ConstGPUProcessorRcPtr getGpuProcessor(const std::string& implName); + + /// Prefix common to all implementation names + static const std::string IMPL_PREFIX; + + /// Prefix common to all node definition names + static const std::string ND_PREFIX; + + /// Prefix common to all node graph names + static const std::string NG_PREFIX; + + protected: + /// Returns a nodedef for a given transform + NodeDefPtr getNodeDef(const ColorSpaceTransform& transform) const override; + + /// Looks for a valid color space name, either in the current config, or using built-in + /// color spaces to find an equivalent one. + const char* getSupportedColorSpaceName(const char* colorSpace) const; + + /// Protected constructor + OpenColorIOManagementSystem(const OCIO::ConstConfigRcPtr& config, const std::string& target, bool useNativeGenerator); + + private: + std::string _target; + OCIO::ConstConfigRcPtr _config; + mutable std::map _implementations; + bool _useNativeGenerator = true; +}; + +MATERIALX_NAMESPACE_END + +#endif +#endif