Skip to content

Commit

Permalink
Shader : Handle ContextProcessors within the shader network
Browse files Browse the repository at this point in the history
This has a big caveat at present : it creates a unique shader in the network for every node/context pair visited. But we should be able to reuse sub-networks when they are seen in different contexts but result in the same thing anyway.
  • Loading branch information
johnhaddon committed Jan 8, 2025
1 parent d39dcaa commit a3d5a4c
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 20 deletions.
112 changes: 112 additions & 0 deletions python/GafferSceneTest/ShaderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,118 @@ def testConnectionFromSwitchIndex( self ) :
network = shader.attributes()["test:surface"]
self.assertEqual( network.outputShader().parameters["i"], IECore.IntData( 0 ) )

def testContextProcessors( self ) :

contextQuery = Gaffer.ContextQuery()
contextQuery.addQuery( Gaffer.Color3fPlug(), "c" )

texture = GafferSceneTest.TestShader( "texture" )
texture["parameters"]["c"].setInput( contextQuery["out"][0]["value"] )

redContext = Gaffer.ContextVariables()
redContext.setup( texture["out"] )
redContext["variables"].addChild( Gaffer.NameValuePlug( "c", imath.Color3f( 1, 0, 0 ) ) )
redContext["in"].setInput( texture["out"] )

greenContext = Gaffer.ContextVariables()
greenContext.setup( texture["out"] )
greenContext["variables"].addChild( Gaffer.NameValuePlug( "c", imath.Color3f( 0, 1, 0 ) ) )
greenContext["in"].setInput( texture["out"] )

mix = GafferSceneTest.TestShader( "mix" )
mix.loadShader( "mix" )
mix["type"].setValue( "test:surface" )
mix["parameters"]["a"].setInput( redContext["out"] )
mix["parameters"]["b"].setInput( greenContext["out"] )

shaderPlug = GafferScene.ShaderPlug()
shaderPlug.setInput( mix["out"] )

network = shaderPlug.attributes()["test:surface"]
self.assertEqual( len( network.shaders() ), 3 )
self.assertEqual( network.getOutput(), ( "mix", "out" ) )

aInput = network.input( ( "mix", "a" ) )
bInput = network.input( ( "mix", "b" ) )

self.assertEqual( network.getShader( aInput.shader ).parameters["c"], IECore.Color3fData( imath.Color3f( 1, 0, 0 ) ) )
self.assertEqual( network.getShader( bInput.shader ).parameters["c"], IECore.Color3fData( imath.Color3f( 0, 1, 0 ) ) )

self.assertEqual( shaderPlug.parameterSource( ( "texture", "c" ) ), texture["parameters"]["c"] )
self.assertEqual( shaderPlug.parameterSource( ( "texture1", "c" ) ), texture["parameters"]["c"] )

def testContextProcessorsWithSpreadsheets( self ) :

spreadsheet = Gaffer.Spreadsheet()
spreadsheet["selector"].setValue( "${color}" )
spreadsheet["rows"].addColumn( Gaffer.Color3fPlug( "c" ) )
spreadsheet["rows"].addRows( 2 )
spreadsheet["rows"][1]["name"].setValue( "red" )
spreadsheet["rows"][1]["cells"]["c"]["value"].setValue( imath.Color3f( 1, 0, 0 ) )
spreadsheet["rows"][2]["name"].setValue( "green" )
spreadsheet["rows"][2]["cells"]["c"]["value"].setValue( imath.Color3f( 0, 1, 0 ) )

texture = GafferSceneTest.TestShader( "texture" )
texture["parameters"]["c"].setInput( spreadsheet["out"]["c"] )

redContext = Gaffer.ContextVariables()
redContext.setup( texture["out"] )
redContext["variables"].addChild( Gaffer.NameValuePlug( "color", "red" ) )
redContext["in"].setInput( texture["out"] )

greenContext = Gaffer.ContextVariables()
greenContext.setup( texture["out"] )
greenContext["variables"].addChild( Gaffer.NameValuePlug( "color", "green" ) )
greenContext["in"].setInput( texture["out"] )

mix = GafferSceneTest.TestShader( "mix" )
mix.loadShader( "mix" )
mix["type"].setValue( "test:surface" )
mix["parameters"]["a"].setInput( redContext["out"] )
mix["parameters"]["b"].setInput( greenContext["out"] )

shaderPlug = GafferScene.ShaderPlug()
shaderPlug.setInput( mix["out"] )

network = shaderPlug.attributes()["test:surface"]
self.assertEqual( len( network.shaders() ), 3 )
self.assertEqual( network.getOutput(), ( "mix", "out" ) )

aInput = network.input( ( "mix", "a" ) )
bInput = network.input( ( "mix", "b" ) )

self.assertEqual( network.getShader( aInput.shader ).parameters["c"], IECore.Color3fData( imath.Color3f( 1, 0, 0 ) ) )
self.assertEqual( network.getShader( bInput.shader ).parameters["c"], IECore.Color3fData( imath.Color3f( 0, 1, 0 ) ) )

@unittest.expectedFailure
def testContextProcessorsWithoutContextSensitiveShaders( self ) :

texture = GafferSceneTest.TestShader( "texture" )

contextA = Gaffer.ContextVariables()
contextA.setup( texture["out"] )
contextA["variables"].addChild( Gaffer.NameValuePlug( "c", "A" ) )
contextA["in"].setInput( texture["out"] )

contextB = Gaffer.ContextVariables()
contextB.setup( texture["out"] )
contextB["variables"].addChild( Gaffer.NameValuePlug( "c", "B" ) )
contextB["in"].setInput( texture["out"] )

mix = GafferSceneTest.TestShader( "mix" )
mix.loadShader( "mix" )
mix["type"].setValue( "test:surface" )
mix["parameters"]["a"].setInput( contextA["out"] )
mix["parameters"]["b"].setInput( contextB["out"] )

# The `texture` shader is the same in both contexts, so there should only
# be a single instance of it in the result.

network = mix.attributes()["test:surface"]
self.assertEqual( len( network.shaders() ), 2 )
self.assertEqual( network.getOutput(), ( "mix", "out" ) )
self.assertEqual( network.input( ( "mix", "a" ) ), network.input( ( "mix", "a" ) ) )

def testSpline( self ) :

n1 = GafferSceneTest.TestShader( "n1" )
Expand Down
84 changes: 64 additions & 20 deletions src/GafferScene/Shader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include "GafferScene/ShaderTweakProxy.h"

#include "Gaffer/ContextProcessor.h"
#include "Gaffer/PlugAlgo.h"
#include "Gaffer/Metadata.h"
#include "Gaffer/NumericPlug.h"
Expand Down Expand Up @@ -168,7 +169,9 @@ struct CycleDetector

IECore::InternedString g_outPlugName( "out" );

const Plug *contextSensitiveSource( const Plug *plug )
/// \todo This is identical to `computedSource()` in Dispatcher.cpp. Consider
/// consolidating them into a single public function in PlugAlgo.
tuple<const Plug *, ConstContextPtr> contextSensitiveSource( const Plug *plug )
{
plug = plug->source();

Expand All @@ -185,10 +188,30 @@ const Plug *contextSensitiveSource( const Plug *plug )
}
}
}
else if( auto contextProcessor = IECore::runTimeCast<const ContextProcessor>( plug->node() ) )
{
if(
contextProcessor->outPlug() &&
( plug == contextProcessor->outPlug() || contextProcessor->outPlug()->isAncestorOf( plug ) )
)
{
ConstContextPtr context = contextProcessor->inPlugContext();
Context::Scope scopedContext( context.get() );
return contextSensitiveSource( contextProcessor->inPlug() );
}
}

return plug;
return make_tuple( plug, Context::current() );
}

struct OptionalScopedContext
{

ConstContextPtr context;
std::optional<Context::Scope> scope;

};

} // namespace

//////////////////////////////////////////////////////////////////////////
Expand All @@ -207,7 +230,8 @@ class Shader::NetworkBuilder

IECore::MurmurHash networkHash()
{
if( const Gaffer::Plug *p = effectiveParameter( m_output ) )
OptionalScopedContext outputContext;
if( const Gaffer::Plug *p = effectiveParameter( m_output, outputContext ) )
{
if( isOutputParameter( p ) )
{
Expand All @@ -227,7 +251,8 @@ class Shader::NetworkBuilder
if( !m_network )
{
m_network = new IECoreScene::ShaderNetwork;
if( const Gaffer::Plug *p = effectiveParameter( m_output ) )
OptionalScopedContext outputContext;
if( const Gaffer::Plug *p = effectiveParameter( m_output, outputContext ) )
{
if( isOutputParameter( p ) )
{
Expand All @@ -246,11 +271,11 @@ class Shader::NetworkBuilder

const ValuePlug *parameterSource( const IECoreScene::ShaderNetwork::Parameter &parameter )
{
for( auto &[shader, handleAndHash] : m_shaders )
for( auto &[key, handleAndHash] : m_shaders )
{
if( handleAndHash.handle == parameter.shader )
{
return shader->parametersPlug()->descendant<ValuePlug>( parameter.name );
return key.first->parametersPlug()->descendant<ValuePlug>( parameter.name );
}
}
return nullptr;
Expand All @@ -261,7 +286,7 @@ class Shader::NetworkBuilder
// Returns the effective shader parameter that should be used taking into account
// enabledPlug() and correspondingInput(). Accepts either output or input parameters
// and may return either an output or input parameter.
const Gaffer::Plug *effectiveParameter( const Gaffer::Plug *parameterPlug ) const
const Gaffer::Plug *effectiveParameter( const Gaffer::Plug *parameterPlug, OptionalScopedContext &parameterContext ) const
{
while( true )
{
Expand All @@ -286,7 +311,7 @@ class Shader::NetworkBuilder
else
{
assert( isInputParameter( parameterPlug ) );
const Gaffer::Plug *source = contextSensitiveSource( parameterPlug );
auto [source, sourceContext] = contextSensitiveSource( parameterPlug );

if( source == parameterPlug || !isParameter( source ) )
{
Expand All @@ -295,6 +320,11 @@ class Shader::NetworkBuilder
else
{
// Follow connection, ready for next iteration.
if( sourceContext != Context::current() )
{
parameterContext.context = sourceContext;
parameterContext.scope.emplace( parameterContext.context.get() );
}
parameterPlug = source;
}
}
Expand Down Expand Up @@ -340,7 +370,8 @@ class Shader::NetworkBuilder

void checkNoShaderInput( const Gaffer::Plug *parameterPlug )
{
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( parameterPlug );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( parameterPlug, parameterContext );
if( effectiveParameter && isOutputParameter( effectiveParameter ) )
{
throw IECore::Exception( fmt::format( "Shader connections to {} are not supported.", parameterPlug->fullName() ) );
Expand All @@ -354,7 +385,7 @@ class Shader::NetworkBuilder

CycleDetector cycleDetector( m_downstreamShaders, shaderNode );

HandleAndHash &handleAndHash = m_shaders[shaderNode];
HandleAndHash &handleAndHash = m_shaders[{shaderNode, Context::current()->hash()}];
if( handleAndHash.hash != IECore::MurmurHash() )
{
return handleAndHash.hash;
Expand All @@ -379,7 +410,9 @@ class Shader::NetworkBuilder

CycleDetector cycleDetector( m_downstreamShaders, shaderNode );

HandleAndHash &handleAndHash = m_shaders[shaderNode];
/// TODO : THIS IS GOING TO BE TOO PESSIMISTIC, AND DUPLICATE SHADERS THAT
/// DON'T ACTUALLY HAVE ANY DIFFERENCES IN CONTEXT.
HandleAndHash &handleAndHash = m_shaders[{shaderNode, Context::current()->hash()}];
if( !handleAndHash.handle.string().empty() )
{
return handleAndHash.handle;
Expand Down Expand Up @@ -489,7 +522,8 @@ class Shader::NetworkBuilder

void hashParameter( const Gaffer::Plug *parameter, IECore::MurmurHash &h )
{
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( parameter );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( parameter, parameterContext );
if( !effectiveParameter )
{
return;
Expand All @@ -512,7 +546,8 @@ class Shader::NetworkBuilder

void addParameter( const Gaffer::Plug *parameter, const IECore::InternedString &parameterName, IECoreScene::Shader *shader, vector<IECoreScene::ShaderNetwork::Connection> &connections )
{
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( parameter );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( parameter, parameterContext );
if( !effectiveParameter )
{
return;
Expand Down Expand Up @@ -565,7 +600,8 @@ class Shader::NetworkBuilder
{
for( Plug::InputIterator it( parameter ); !it.done(); ++it )
{
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( it->get() );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( it->get(), parameterContext );
if( effectiveParameter && isOutputParameter( effectiveParameter ) )
{
parameterHashForPlug( effectiveParameter, h );
Expand Down Expand Up @@ -599,7 +635,8 @@ class Shader::NetworkBuilder
checkNoShaderInput( parameter->pointXPlug( i ) );

const auto* yPlug = parameter->pointYPlug( i );
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( yPlug );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( yPlug, parameterContext );
if( effectiveParameter && isOutputParameter( effectiveParameter ) )
{
hasInput = true;
Expand All @@ -610,7 +647,8 @@ class Shader::NetworkBuilder
{
for( Plug::InputIterator it( yPlug ); !it.done(); ++it )
{
const Gaffer::Plug *effectiveCompParameter = this->effectiveParameter( it->get() );
parameterContext.scope.reset();
const Gaffer::Plug *effectiveCompParameter = this->effectiveParameter( it->get(), parameterContext );
if( effectiveCompParameter && isOutputParameter( effectiveCompParameter ) )
{
hasInput = true;
Expand All @@ -637,7 +675,8 @@ class Shader::NetworkBuilder
{
for( Plug::InputIterator it( parameter ); !it.done(); ++it )
{
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( it->get() );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( it->get(), parameterContext );
if( effectiveParameter && isOutputParameter( effectiveParameter ) )
{
IECore::InternedString inputName = parameterName.string() + "." + (*it)->getName().string();
Expand Down Expand Up @@ -672,7 +711,8 @@ class Shader::NetworkBuilder
for( int i = 0; i < n; i++ )
{
const auto* yPlug = parameter->pointYPlug( i );
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( yPlug );
OptionalScopedContext parameterContext;
const Gaffer::Plug *effectiveParameter = this->effectiveParameter( yPlug, parameterContext );
if( effectiveParameter && isOutputParameter( effectiveParameter ) )
{
inputs.push_back( std::make_tuple( i, "", effectiveParameter ) );
Expand All @@ -681,9 +721,11 @@ class Shader::NetworkBuilder
{
for( Plug::InputIterator it( yPlug ); !it.done(); ++it )
{
const Gaffer::Plug *effectiveCompParameter = this->effectiveParameter( it->get() );
parameterContext.scope.reset();
const Gaffer::Plug *effectiveCompParameter = this->effectiveParameter( it->get(), parameterContext );
if( effectiveCompParameter && isOutputParameter( effectiveCompParameter ) )
{
/// \todo DO WE NEED TO USE THE PARAMETER CONTEXT HERE???
inputs.push_back( std::make_tuple( i, "." + (*it)->getName().string(), effectiveCompParameter ) );
}
}
Expand Down Expand Up @@ -736,6 +778,7 @@ class Shader::NetworkBuilder

for( const auto &[ origIndex, componentSuffix, sourcePlug ] : inputs )
{
/// \todo PROBABLY NEED TO SCOPE THE CONTEXT HERE?
IECoreScene::ShaderNetwork::Parameter sourceParameter = outputParameterForPlug( sourcePlug );

int index = applySort[ origIndex ];
Expand Down Expand Up @@ -778,7 +821,8 @@ class Shader::NetworkBuilder
IECore::MurmurHash hash;
};

using ShaderMap = std::map<const Shader *, HandleAndHash>;
using ShaderMapKey = std::pair<const Shader *, IECore::MurmurHash>;
using ShaderMap = std::map<ShaderMapKey, HandleAndHash>;
ShaderMap m_shaders;

ShaderSet m_downstreamShaders; // Used for detecting cycles
Expand Down
6 changes: 6 additions & 0 deletions src/GafferSceneTest/TestShader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,10 @@ void TestShader::loadShader( const std::string &shaderName, bool keepExistingVal
setupTypedPlug<SplinefColor3fPlug>( "spline", parametersPlug, SplineDefinitionfColor3f() );
setupOptionalValuePlug<StringPlug>( "optionalString", parametersPlug, new StringPlug() );
}
else if( shaderName == "mix" )
{
setupTypedPlug<Color3fPlug>( "a", parametersPlug, Imath::Color3f( 0.f ) );
setupTypedPlug<Color3fPlug>( "b", parametersPlug, Imath::Color3f( 0.f ) );
setupTypedPlug<FloatPlug>( "mix", parametersPlug, 0.5 );
}
}

0 comments on commit a3d5a4c

Please sign in to comment.