Skip to content

Commit

Permalink
OSL Expressions : Support vector types in context
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmehl committed Oct 27, 2023
1 parent 0f7cdc8 commit 2caea38
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 37 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Improvements
------------

- LightTool : Changed spot light and quad light edge tool tip locations so that they follow the cone and edge during drag.
- OSL Expression : Added support for getting an element of a vector typed context variable using `context( variableName, index )` or `context( variableName, index, defaultValue )`. Negative a negative index can be used to get elements starting at the end of the array.

Fixes
-----
Expand Down
137 changes: 137 additions & 0 deletions python/GafferOSLTest/OSLExpressionEngineTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,143 @@ def testContextTypes( self ) :
self.assertEqual( s["n"]["user"]["s"].getValue(), "non-default" )
self.assertEqual( s["n"]["user"]["b"].getValue(), True )

def testContextVectorTypes( self ) :

s = Gaffer.ScriptNode()
s["n"] = Gaffer.Node()
for i in range( 0, 4 ) :
s["n"]["user"]["f" + str( i )] = Gaffer.FloatPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["i" + str( i )] = Gaffer.IntPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["c" + str( i )] = Gaffer.Color3fPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["v" + str( i )] = Gaffer.V3fPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["s" + str( i )] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["b" + str( i )] = Gaffer.BoolPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )

s["e"] = Gaffer.Expression()
s["e"].setExpression(
inspect.cleandoc(
"""
parent.n.user.f0 = context( "f", 0, 1 );
parent.n.user.i0 = context( "i", 0, 1 );
parent.n.user.c0 = context( "c", 0, color( 1, 2, 3 ) );
parent.n.user.v0 = context( "v", 0 );
parent.n.user.s0 = context( "s", 0, "default0" );
parent.n.user.f1 = context( "f", 1, 2 );
parent.n.user.i1 = context( "i", 1, 2 );
parent.n.user.c1 = context( "c", 1, color( 4, 5, 6 ) );
parent.n.user.v1 = context( "v", 1 );
parent.n.user.s1 = context( "s", 1, "default1" );
parent.n.user.f2 = context( "f", 2, 3 );
parent.n.user.i2 = context( "i", 2, 3 );
parent.n.user.c2 = context( "c", 2, color( 7, 8, 9 ) );
parent.n.user.v2 = context( "v", 2 );
parent.n.user.s2 = context( "s", 2, "default2" );
parent.n.user.f3 = context( "f", -1 );
parent.n.user.i3 = context( "i", -1 );
parent.n.user.c3 = context( "c", -1 );
parent.n.user.v3 = context( "v", -1 );
parent.n.user.s3 = context( "s", -1 );
"""
),
"OSL"
)

with Gaffer.Context() as c :

self.assertEqual( s["n"]["user"]["f0"].getValue(), 1 )
self.assertEqual( s["n"]["user"]["i0"].getValue(), 1 )
self.assertEqual( s["n"]["user"]["c0"].getValue(), imath.Color3f( 1, 2, 3 ) )
self.assertEqual( s["n"]["user"]["v0"].getValue(), imath.V3f( 0 ) )
self.assertEqual( s["n"]["user"]["s0"].getValue(), "default0" )

self.assertEqual( s["n"]["user"]["f1"].getValue(), 2 )
self.assertEqual( s["n"]["user"]["i1"].getValue(), 2 )
self.assertEqual( s["n"]["user"]["c1"].getValue(), imath.Color3f( 4, 5, 6 ) )
self.assertEqual( s["n"]["user"]["v1"].getValue(), imath.V3f( 0 ) )
self.assertEqual( s["n"]["user"]["s1"].getValue(), "default1" )

self.assertEqual( s["n"]["user"]["f2"].getValue(), 3 )
self.assertEqual( s["n"]["user"]["i2"].getValue(), 3 )
self.assertEqual( s["n"]["user"]["c2"].getValue(), imath.Color3f( 7, 8, 9 ) )
self.assertEqual( s["n"]["user"]["v2"].getValue(), imath.V3f( 0 ) )
self.assertEqual( s["n"]["user"]["s2"].getValue(), "default2" )

self.assertEqual( s["n"]["user"]["f3"].getValue(), 0 )
self.assertEqual( s["n"]["user"]["i3"].getValue(), 0 )
self.assertEqual( s["n"]["user"]["c3"].getValue(), imath.Color3f( 0 ) )
self.assertEqual( s["n"]["user"]["v3"].getValue(), imath.V3f( 0 ) )
self.assertEqual( s["n"]["user"]["s3"].getValue(), "" )

c["f"] = IECore.FloatVectorData( [ 10, 11 ] )
c["i"] = IECore.IntVectorData( [ 11, 12 ] )
c["c"] = IECore.Color3fVectorData( [ imath.Color3f( 10, 11, 12 ), imath.Color3f( 13, 14, 15 ) ] )
c["v"] = IECore.V3fVectorData( [ imath.V3f( 9, 10, 11 ), imath.V3f( 12, 13, 14 ) ] )
c["s"] = IECore.StringVectorData( [ "non-default0", "non-default1" ] )

self.assertEqual( s["n"]["user"]["f0"].getValue(), 10 )
self.assertEqual( s["n"]["user"]["i0"].getValue(), 11 )
self.assertEqual( s["n"]["user"]["c0"].getValue(), imath.Color3f( 10, 11, 12 ) )
self.assertEqual( s["n"]["user"]["v0"].getValue(), imath.V3f( 9, 10, 11 ) )
self.assertEqual( s["n"]["user"]["s0"].getValue(), "non-default0" )

self.assertEqual( s["n"]["user"]["f1"].getValue(), 11 )
self.assertEqual( s["n"]["user"]["i1"].getValue(), 12 )
self.assertEqual( s["n"]["user"]["c1"].getValue(), imath.Color3f( 13, 14, 15 ) )
self.assertEqual( s["n"]["user"]["v1"].getValue(), imath.V3f( 12, 13, 14 ) )
self.assertEqual( s["n"]["user"]["s1"].getValue(), "non-default1" )

self.assertEqual( s["n"]["user"]["f2"].getValue(), 3 )
self.assertEqual( s["n"]["user"]["i2"].getValue(), 3 )
self.assertEqual( s["n"]["user"]["c2"].getValue(), imath.Color3f( 7, 8, 9 ) )
self.assertEqual( s["n"]["user"]["v2"].getValue(), imath.V3f( 0 ) )
self.assertEqual( s["n"]["user"]["s2"].getValue(), "default2" )

self.assertEqual( s["n"]["user"]["f3"].getValue(), 11 )
self.assertEqual( s["n"]["user"]["i3"].getValue(), 12 )
self.assertEqual( s["n"]["user"]["c3"].getValue(), imath.Color3f( 13, 14, 15 ) )
self.assertEqual( s["n"]["user"]["v3"].getValue(), imath.V3f( 12, 13, 14 ) )
self.assertEqual( s["n"]["user"]["s3"].getValue(), "non-default1" )

def testScenePathContext( self ) :

s = Gaffer.ScriptNode()
s["n"] = Gaffer.Node()
# OSL can't set a `StringVectorDataPlug`, so we set a boolean plug on a successful string match
s["n"]["user"]["p1"] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["p2"] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["p3"] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
s["n"]["user"]["p4"] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )

s["e"] = Gaffer.Expression()
s["e"].setExpression(
inspect.cleandoc(
"""
parent.n.user.p1 = context( "scene:path", 0, "noPath1" );
parent.n.user.p2 = context( "scene:path", 1, "noPath2" );
parent.n.user.p3 = context( "scene:path", 2 );
parent.n.user.p4 = context( "scene:path", 3 );
"""
),
"OSL"
)

with Gaffer.Context() as c :

self.assertEqual( s["n"]["user"]["p1"].getValue(), "noPath1" )
self.assertEqual( s["n"]["user"]["p2"].getValue(), "noPath2" )
self.assertEqual( s["n"]["user"]["p3"].getValue(), "" )
self.assertEqual( s["n"]["user"]["p4"].getValue(), "" )

c["scene:path"] = IECore.InternedStringVectorData( [ "yellow", "brick", "road" ] )

self.assertEqual( s["n"]["user"]["p1"].getValue(), "yellow" )
self.assertEqual( s["n"]["user"]["p2"].getValue(), "brick" )
self.assertEqual( s["n"]["user"]["p3"].getValue(), "road" )
self.assertEqual( s["n"]["user"]["p4"].getValue(), "" )

def testDefaultExpression( self ) :

s = Gaffer.ScriptNode()
Expand Down
62 changes: 62 additions & 0 deletions shaders/GafferOSL/Expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,66 @@ string context( string name )
return context( name, "" );
}

// Vector context variable queries with index

int context( string name, int index, int defaultValue )
{
int result = defaultValue;
getattribute( "gaffer:context", name, index, result );
return result;
}

int context( string name, int index )
{
return context( name, index, 0 );
}

float context( string name, int index, float defaultValue )
{
float result = defaultValue;
getattribute( "gaffer:context", name, index, result );
return result;
}

float context( string name, int index )
{
return context( name, index, 0.0 );
}

color context( string name, int index, color defaultValue )
{
color result = defaultValue;
getattribute( "gaffer:context", name, index, result );
return result;
}

color context( string name, int index )
{
return context( name, index, color( 0.0 ) );
}

vector context( string name, int index, vector defaultValue )
{
vector result = defaultValue;
getattribute( "gaffer:context", name, index, result );
return result;
}

vector context( string name, int index )
{
return context( name, index, vector( 0.0 ) );
}

string context( string name, int index, string defaultValue )
{
string result = defaultValue;
getattribute( "gaffer:context", name, index, result );
return result;
}

string context( string name, int index )
{
return context( name, index, "" );
}

#endif // GAFFEROSL_EXPRESSION_H
100 changes: 63 additions & 37 deletions src/GafferOSL/OSLExpressionEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@

#include "IECoreImage/OpenImageIOAlgo.h"

#include "IECore/DataAlgo.h"
#include "IECore/SearchPath.h"
#include "IECore/StringAlgo.h"
#include "IECore/TypeTraits.h"

#include "OSL/oslcomp.h"
#include "OSL/oslexec.h"
Expand Down Expand Up @@ -102,6 +104,65 @@ struct RenderState
// our RenderState.
//////////////////////////////////////////////////////////////////////////


bool getAttributeInternal( OSL::ShaderGlobals *sg, bool derivatives, ustring object, TypeDesc type, ustring name, std::optional<int> index, void *value )
{
const RenderState *renderState = sg ? static_cast<RenderState *>( sg->renderstate ) : nullptr;
if( !renderState )
{
return false;
}

// TODO - might be nice if there was some way to speed this up by directly querying the type matching
// the TypeDesc, instead of getting as a generic Data?
DataPtr data = renderState->context->getAsData( name.c_str(), nullptr );
if( !data )
{
return false;
}

if( derivatives )
{
memset( (char*)value + type.size(), 0, 2 * type.size() );
}

IECoreImage::OpenImageIOAlgo::DataView dataView( data.get(), /* createUStrings = */ true );
if( !dataView.data )
{
if( auto b = runTimeCast<BoolData>( data.get() ) )
{
// BoolData isn't supported by `DataView` because `OIIO::TypeDesc` doesn't
// have a boolean type. We could work around this in `DataView` by casting to
// `TypeDesc::UCHAR` (along with a `static_assert( sizeof( bool ) == 1`). But that
// wouldn't be round-trippable via `OpenImageIOAlgo::data()`, so it's not clear
// that it would be a good thing in general. Here we don't care about round
// tripping, so we simply perform a conversion ourselves.
const unsigned char c = b->readable();
return ShadingSystem::convert_value( value, type, &c, TypeDesc::UCHAR );
}
return false;
}

int effectiveIndex = 0;
if( index )
{
effectiveIndex = index.value() < 0 ? dataView.type.arraylen + index.value() : index.value();
if( effectiveIndex >= dataView.type.arraylen )
{
return false;
}
dataView.type.arraylen = 0;
}

return ShadingSystem::convert_value(
value,
type,
(char *)dataView.data + dataView.type.size() * effectiveIndex,
dataView.type
);
}


/// \todo Share with OSLRenderer

class RendererServices : public OSL::RendererServices
Expand Down Expand Up @@ -135,47 +196,12 @@ class RendererServices : public OSL::RendererServices

bool get_attribute( OSL::ShaderGlobals *sg, bool derivatives, ustring object, TypeDesc type, ustring name, void *value ) override
{
const RenderState *renderState = sg ? static_cast<RenderState *>( sg->renderstate ) : nullptr;
if( !renderState )
{
return false;
}

// TODO - might be nice if there was some way to speed this up by directly querying the type matching
// the TypeDesc, instead of getting as a generic Data?
const DataPtr data = renderState->context->getAsData( name.c_str(), nullptr );
if( !data )
{
return false;
}

if( derivatives )
{
memset( (char*)value + type.size(), 0, 2 * type.size() );
}

IECoreImage::OpenImageIOAlgo::DataView dataView( data.get(), /* createUStrings = */ true );
if( !dataView.data )
{
if( auto b = runTimeCast<BoolData>( data.get() ) )
{
// BoolData isn't supported by `DataView` because `OIIO::TypeDesc` doesn't
// have a boolean type. We could work around this in `DataView` by casting to
// `TypeDesc::UCHAR` (along with a `static_assert( sizeof( bool ) == 1`). But that
// wouldn't be round-trippable via `OpenImageIOAlgo::data()`, so it's not clear
// that it would be a good thing in general. Here we don't care about round
// tripping, so we simply perform a conversion ourselves.
const unsigned char c = b->readable();
return ShadingSystem::convert_value( value, type, &c, TypeDesc::UCHAR );
}
return false;
}
return ShadingSystem::convert_value( value, type, dataView.data, dataView.type );
return getAttributeInternal( sg, derivatives, object, type, name, std::nullopt, value );
}

bool get_array_attribute( OSL::ShaderGlobals *sg, bool derivatives, ustring object, TypeDesc type, ustring name, int index, void *value ) override
{
return false;
return getAttributeInternal( sg, derivatives, object, type, name, index, value );
}

// OSL tries to populate shader parameter values per-object by calling this method.
Expand Down

0 comments on commit 2caea38

Please sign in to comment.