Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
testRunner: runuser -u testUser --
sconsCacheMegabytes: 400
jobs: 4
dependenciesURL: https://github.com/ImageEngine/cortex/releases/download/10.7.0.0a14/cortex-10.7.0.0a14-linux-platform24.tar.gz
dependenciesURL: https://github.com/ImageEngine/cortex/releases/download/10.7.0.0/cortex-10.7.0.0-linux-platform24.tar.gz
extraBuildArguments: CYCLES_ROOT=""

- name: windows
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/main/installDependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

# Determine default archive URL.

defaultURL = "https://github.com/ImageEngine/cortex/releases/download/10.7.0.0a14/cortex-10.7.0.0a14-{platform}-{vfxPlatform}.{extension}"
defaultURL = "https://github.com/ImageEngine/cortex/releases/download/10.7.0.0/cortex-10.7.0.0-{platform}-{vfxPlatform}.{extension}"

# Parse command line arguments.

Expand Down Expand Up @@ -85,8 +85,7 @@
args = parser.parse_args()

archiveURL = args.archiveURL.format(
## \todo Rename "macos-arm64" Cortex release to "macos" to match GafferDependencies.
platform = { "darwin" : "macos-arm64", "win32" : "windows" }.get( sys.platform, "linux" ),
platform = { "darwin" : "macos", "win32" : "windows" }.get( sys.platform, "linux" ),
vfxPlatform = args.vfxPlatform,
extension = "tar.gz" if sys.platform != "win32" else "zip"
)
Expand Down
5 changes: 5 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ Fixes
- Fixed handling of connections between floats and color/vector components [^2].
- Fixed bug preventing attributes from being deleted from lights during an interactive render [^2].

Build
-----

- Cortex : Updated to version 10.7.0.0.

[^1]: Improvement to a feature introduced in `1.7.0.0a1`, so should be omitted from final `1.7.0.0` release notes.
[^2]: Included in `1.6.x.x`, so should be omitted from final `1.7.0.0` release notes.

Expand Down
8 changes: 7 additions & 1 deletion python/GafferArnoldTest/ArnoldRenderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import pathlib
import inspect
import unittest
import subprocess
import threading

import arnold
Expand Down Expand Up @@ -1796,6 +1795,13 @@ def _createDistantLight( self ) :
light.loadShader( "distant_light" )
return light, light["parameters"]["color"]

def _createColorAttributeReader( self, attributeName ) :

shader = GafferArnold.ArnoldShader()
shader.loadShader( "user_data_rgb" )
shader["parameters"]["attribute"].setValue( attributeName )
return shader, shader["out"]

def _cameraVisibilityAttribute( self ) :

return "ai:visibility:camera"
Expand Down
9 changes: 9 additions & 0 deletions python/GafferDelightTest/DelightRenderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ def _createConstantShader( self ) :
shader.loadShader( "Surface/Constant" )
return shader, shader["parameters"]["Cs"], shader["out"]["out"]

def _createColorAttributeReader( self, attributeName ) :

shader = GafferOSL.OSLShader()
shader.loadShader( "dlPrimitiveAttribute" )
shader["parameters"]["attribute_name"].setValue( attributeName )
shader["parameters"]["attribute_type"].setValue( 1 ) # color

return shader, shader["out"]["o_color"]

def _createOptions( self ) :

# Improve anti-aliasing for motion-blur tests.
Expand Down
10 changes: 10 additions & 0 deletions python/GafferRenderManTest/RenderManRenderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import Gaffer
import GafferTest
import GafferScene
import GafferOSL
import GafferRenderMan
import GafferSceneTest

Expand Down Expand Up @@ -135,6 +136,15 @@ def _createDistantLight( self ) :
light.loadShader( "PxrDistantLight" )
return light, light["parameters"]["lightColor"]

def _createColorAttributeReader( self, attributeName ) :

shader = GafferOSL.OSLShader()
shader.loadShader( "PxrAttribute" )
shader["parameters"]["varname"].setValue( attributeName )
shader["parameters"]["type"].setValue( "color" )

return shader, shader["out"]["resultRGB"]

def _cameraVisibilityAttribute( self ) :

return "ri:visibility:camera"
Expand Down
19 changes: 19 additions & 0 deletions python/GafferSceneTest/PointInstancerAlgoTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ def testFlatten( self ) :
pointInstancer.setPosition( IECore.V3fVectorData( [ imath.V3f( 1, 2, 3 ), imath.V3f( 1, 0, 0 ) ] ) )
pointInstancer.setPrototypeIndex( IECore.IntVectorData( [ 0, 1 ] ) )
pointInstancer.setPrototypes( IECore.StringVectorData( [ "prototypes/prototype1", "prototypes/prototype2" ] ) )
pointInstancer["intPrimitiveVariable"] = IECoreScene.PrimitiveVariable(
IECoreScene.PrimitiveVariable.Interpolation.Vertex,
IECore.IntVectorData( [ 1, 2 ] )
)
pointInstancer["stringPrimitiveVariable"] = IECoreScene.PrimitiveVariable(
IECoreScene.PrimitiveVariable.Interpolation.Vertex,
IECore.StringVectorData( [ "apple", "pear" ] )
)
pointInstancer["v3fPrimitiveVariable"] = IECoreScene.PrimitiveVariable(
IECoreScene.PrimitiveVariable.Interpolation.Vertex,
IECore.V3fVectorData( [ imath.V3f( 0 ), imath.V3f( 1 ) ], IECore.GeometricData.Interpretation.Normal )
)

pointInstancerNode = GafferScene.ObjectToScene()
pointInstancerNode["object"].setValue( pointInstancer )
Expand Down Expand Up @@ -256,6 +268,13 @@ def testFlatten( self ) :
]
)

self.assertEqual( flattened["intPrimitiveVariable"].data, IECore.IntVectorData( [ 1, 1, 1, 2, 2 ] ) )
self.assertEqual( flattened["stringPrimitiveVariable"].data, IECore.StringVectorData( [ "apple", "apple", "apple", "pear", "pear" ] ) )
self.assertEqual(
flattened["v3fPrimitiveVariable"].data,
IECore.V3fVectorData( [ imath.V3f( 0 ), imath.V3f( 0 ), imath.V3f( 0 ), imath.V3f( 1 ), imath.V3f( 1 ) ], IECore.GeometricData.Interpretation.Normal )
)

@GafferTest.TestRunner.CategorisedTestMethod( { "pointInstancer" } )
def testFlattenIncludesRootTransform( self ) :

Expand Down
101 changes: 101 additions & 0 deletions python/GafferSceneTest/RenderTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,100 @@ def testPointInstancerMotionBlur( self ) :
sampler["pixel"].setValue( imath.V2f( x, y ) )
self.assertEqualWithAbsError( sampler["color"]["a"].getValue(), 0, 0.005 )

@GafferTest.TestRunner.CategorisedTestMethod( { "pointInstancer" } )
def testPointInstancerInstanceAttributes( self ) :

if not self.pointInstancerSupported :
raise unittest.SkipTest( "PointInstancer not supported" )

script = Gaffer.ScriptNode()

pointInstancer = IECoreScene.PointInstancer( 2 )
pointInstancer.setPosition(
IECore.V3fVectorData( [
imath.V3f( -1, 0, 0 ), imath.V3f( 1, 0, 0 ),
] )
)
pointInstancer.setPrototypeIndex( IECore.IntVectorData( [ 0, 0 ] ) )
pointInstancer.setPrototypes( IECore.StringVectorData( [ "./sphere" ] ) )
pointInstancer["myColor"] = IECoreScene.PrimitiveVariable(
IECoreScene.PrimitiveVariable.Interpolation.Vertex,
IECore.Color3fVectorData( [ imath.Color3f( 1, 0, 0 ), imath.Color3f( 0, 1, 0 ) ] )
)

script["pointInstancer"] = GafferScene.ObjectToScene()
script["pointInstancer"]["name"].setValue( "instancer" )
script["pointInstancer"]["object"].setValue( pointInstancer )

script["sphere"] = GafferScene.Sphere()
script["sphere"]["radius"].setValue( 0.5 )

script["attributeReader"], attributeReaderOut = self._createColorAttributeReader( "myColor" )
script["constantShader"], colorPlug, constantShaderOut = self._createConstantShader()
colorPlug.setInput( attributeReaderOut )

script["shaderAssignment"] = GafferScene.ShaderAssignment()
script["shaderAssignment"]["in"].setInput( script["sphere"]["out"] )
script["shaderAssignment"]["shader"].setInput( constantShaderOut )

script["prototypeParent"] = GafferScene.Parent()
script["prototypeParent"]["in"].setInput( script["pointInstancer"]["out"] )
script["prototypeParent"]["children"][0].setInput( script["shaderAssignment"]["out"] )
script["prototypeParent"]["parent"].setValue( "/instancer" )

script["camera"] = GafferScene.Camera()
script["camera"]["transform"]["translate"]["z"].setValue( 5 )

script["parent"] = GafferScene.Parent()
script["parent"]["in"].setInput( script["prototypeParent"]["out"] )
script["parent"]["children"][0].setInput( script["camera"]["out"] )
script["parent"]["parent"].setValue( "/" )

imagePath = self.temporaryDirectory() / "test.exr"

script["outputs"] = GafferScene.Outputs()
script["outputs"].addOutput(
"beauty",
IECoreScene.Output(
imagePath.as_posix(),
"exr",
"rgba"
)
)

script["outputs"]["in"].setInput( script["parent"]["out"] )

script["options"] = GafferScene.StandardOptions()
script["options"]["in"].setInput( script["outputs"]["out"] )
script["options"]["options"]["render:camera"]["enabled"].setValue( True )
script["options"]["options"]["render:camera"]["value"].setValue( "/camera" )

script["rendererOptions"] = self._createOptions()
script["rendererOptions"]["in"].setInput( script["options"]["out"] )

script["render"] = GafferScene.Render()
script["render"]["in"].setInput( script["rendererOptions"]["out"] )
script["render"]["renderer"].setValue( self.renderer )

reader = GafferImage.ImageReader()
reader["fileName"].setValue( imagePath )

sampler = GafferImage.ImageSampler()
sampler["image"].setInput( reader["out"] )

script["render"]["task"].execute()

for centre, expectedColor in [
[ imath.V2f( 180, 240 ), imath.Color4f( 1, 0, 0, 1 ) ],
[ imath.V2f( 456, 240 ), imath.Color4f( 0, 1, 0, 1 ) ],
] :

with self.subTest( centre = centre ) :

# Assert there's an instance where we expect it.
sampler["pixel"].setValue( centre )
self.assertEqual( sampler["color"].getValue(), expectedColor )

## Should be implemented by derived classes to return
# an appropriate Shader node with a constant surface shader loaded, along
# with the plug for the colour parameter and the output plug to be connected
Expand All @@ -1614,6 +1708,13 @@ def _createDistantLight( self ) :

raise NotImplementedError

# Should be implemented by derived classes to return a Shader node
# which will read the requested attribute, along with the output plug
# from that node.
def _createColorAttributeReader( self, attributeName ) :

raise NotImplementedError

## Should be implemented by derived classes to return
# the name of a bool attribute which controls camera visibility.
def _cameraVisibilityAttribute( self ) :
Expand Down
64 changes: 64 additions & 0 deletions src/GafferScene/PointInstancerAlgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

#include "GafferScene/SceneAlgo.h"

#include "IECore/DataAlgo.h"
#include "IECore/NullObject.h"

#include "Imath/ImathMatrixAlgo.h"
Expand Down Expand Up @@ -266,6 +267,47 @@ FlattenedPrototype flattenedPrototype( const Private::RendererAlgo::RenderOption
return result;
}

struct FlattenedPrimitiveVariable
{
using FlattenFunction = std::function<void ( size_t sourceIndex, size_t flattenedIndex, size_t numFlattenedPoints )>;
DataPtr data;
FlattenFunction flattenFunction;
};

FlattenedPrimitiveVariable createFlattenedPrimitiveVariable( const PrimitiveVariable &primitiveVariable, size_t flattenedSize )
{
FlattenedPrimitiveVariable result;
IECore::dispatch(

primitiveVariable.data.get(),

[&]( auto typedData ) -> void {

using DataType = remove_const_t<remove_pointer_t<decltype( typedData )>>;

if constexpr( TypeTraits::IsVectorTypedData<DataType>::value )
{
typename DataType::Ptr flattenedData = new DataType;
flattenedData->writable().resize( flattenedSize );
result.data = flattenedData;
if constexpr( TypeTraits::IsGeometricTypedData<DataType>::value )
{
flattenedData->setInterpretation( typedData->getInterpretation() );
}

using ElementType = typename DataType::ValueType::value_type;
PrimitiveVariable::IndexedView<ElementType> indexedView( primitiveVariable );

result.flattenFunction = [indexedView, &out=flattenedData->writable()] ( size_t sourceIndex, size_t flattenedIndex, size_t numFlattenedPoints ) {
std::fill( out.begin() + flattenedIndex, out.begin() + flattenedIndex + numFlattenedPoints, indexedView[sourceIndex] );
};
}
}

);
return result;
}

} // namespace

IECoreScene::PointInstancerPtr Private::PointInstancerAlgo::flatten( const IECoreScene::PointInstancer *instancer, const RendererAlgo::RenderOptions &renderOptions, const ScenePlug *scene )
Expand Down Expand Up @@ -380,6 +422,17 @@ IECoreScene::PointInstancerPtr Private::PointInstancerAlgo::flatten( const IECor
auto &flattenedOrientation = flattenedOrientationData->writable();
flattenedOrientation.resize( numFlattenedPoints );

vector<FlattenedPrimitiveVariable::FlattenFunction> primitiveVariableFunctions;
for( const auto &[name, primitiveVariable] : instancer->instanceAttributes() )
{
FlattenedPrimitiveVariable f = createFlattenedPrimitiveVariable( primitiveVariable, numFlattenedPoints );
if( f.data )
{
primitiveVariableFunctions.push_back( f.flattenFunction );
result->variables[name] = PrimitiveVariable( primitiveVariable.interpolation, f.data );
}
}

// Fill the vertex data, parallelising across points.

PointInstancer::TransformQuery transformQuery( *instancer );
Expand All @@ -397,6 +450,7 @@ IECoreScene::PointInstancerPtr Private::PointInstancerAlgo::flatten( const IECor
{
continue;
}

size_t flattenedPointIndex = pointOffsets[pointIndex];
for( const auto &location : flattenedPrototypes[prototypeIndex[pointIndex]] )
{
Expand All @@ -411,6 +465,16 @@ IECoreScene::PointInstancerPtr Private::PointInstancerAlgo::flatten( const IECor

flattenedPointIndex++;
}

for( const auto &f : primitiveVariableFunctions )
{
/// \todo Investigate the `std::function` call overhead,
/// which we are paying for each source point. We might be
/// faster dispatching per primitive variable instead, but
/// then we would need a different approach to the
/// threading.
f( pointIndex, pointOffsets[pointIndex], flattenedPrototypes[prototypeIndex[pointIndex]].size() );
}
}

},
Expand Down
9 changes: 9 additions & 0 deletions src/IECoreArnold/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2804,6 +2804,15 @@ class PointInstancerCache : public IECore::RefCounted
AiNodeSetArray( instancerNode.get(), g_instanceVisibilityArnoldString, visibilityArray );
AiNodeSetArray( instancerNode.get(), g_instanceShaderArnoldString, shaderArray );

// Add instance attributes

for( const auto &[name, primitiveVariable] : samples[0]->instanceAttributes() )
{
string prefixedName = "instance_" + name;
IECore::ConstDataPtr data = primitiveVariable.expandedData();
ParameterAlgo::setParameter( instancerNode.get(), AtString( prefixedName.c_str() ), data.get() );
}

AiNodeSetByte( instancerNode.get(), g_visibilityArnoldString, 0 );

return std::make_shared<InstancerNodes>( InstancerNodes{
Expand Down
9 changes: 9 additions & 0 deletions src/IECoreDelight/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,15 @@ class PointInstancerCache : public IECore::RefCounted
ParameterList parameters;
parameters.add( "modelindices", prototypeIndex.get() );

vector<ConstDataPtr> instanceAttributeData;
for( const auto &[name, value] : samples[0]->instanceAttributes() )
{
ConstDataPtr d = value.expandedData();
parameters.add( name.c_str(), d.get() );
// Keep alive until `NSISetAttribute()` call.
instanceAttributeData.push_back( d );
}

NSISetAttribute( m_context, handle, parameters.size(), parameters.data() );

// Convert prototypes and connect to `sourcemodels` attribute.
Expand Down
Loading
Loading