Exemple #1
0
IECore::TransformPtr GafferScene::transform( const ScenePlug *scene, const ScenePlug::ScenePath &path, const Imath::V2f &shutter, bool motionBlur )
{
	int numSamples = 1;
	if( motionBlur )
	{
		ConstCompoundObjectPtr attributes = scene->fullAttributes( path );
		const IntData *transformBlurSegmentsData = attributes->member<IntData>( "gaffer:transformBlurSegments" );
		numSamples = transformBlurSegmentsData ? transformBlurSegmentsData->readable() + 1 : 2;

		const BoolData *transformBlurData = attributes->member<BoolData>( "gaffer:transformBlur" );
		if( transformBlurData && !transformBlurData->readable() )
		{
			numSamples = 1;
		}
	}

	MatrixMotionTransformPtr result = new MatrixMotionTransform();
	ContextPtr transformContext = new Context( *Context::current(), Context::Borrowed );
	Context::Scope scopedContext( transformContext.get() );
	for( int i = 0; i < numSamples; i++ )
	{
		float frame = lerp( shutter[0], shutter[1], (float)i / std::max( 1, numSamples - 1 ) );
		transformContext->setFrame( frame );
		result->snapshots()[frame] = scene->fullTransform( path );
	}

	return result;
}
Exemple #2
0
Imath::Box3f SceneProcedural::bound() const
{
	/// \todo I think we should be able to remove this exception handling in the future.
	/// Either when we do better error handling in ValuePlug computations, or when 
	/// the bug in IECoreGL that caused the crashes in SceneProceduralTest.testComputationErrors
	/// is fixed.
	try
	{
		ContextPtr timeContext = new Context( *m_context );
		Context::Scope scopedTimeContext( timeContext );
		
		/// \todo This doesn't take account of the unfortunate fact that our children may have differing
		/// numbers of segments than ourselves. To get an accurate bound we would need to know the different sample
		/// times the children may be using and evaluate a bound at those times as well. We don't want to visit
		/// the children to find the sample times out though, because that defeats the entire point of deferred loading.
		///
		/// Here are some possible approaches :
		///
		/// 1) Add a new attribute called boundSegments, which defines the number of segments used to calculate
		///    the bounding box. It would be the responsibility of the user to set this to an appropriate value
		///    at the parent levels, so that the parents calculate bounds appropriate for the children.
		///    This seems like a bit too much burden on the user.
		///
		/// 2) Add a global option called "maxSegments" - this will clamp the number of segments used on anything
		///    and will be set to 1 by default. The user will need to increase it to allow the leaf level attributes
		///    to take effect, and all bounding boxes everywhere will be calculated using that number of segments
		///    (actually I think it'll be that number of segments and all nondivisible smaller numbers). This should
		///    be accurate but potentially slower, because we'll be doing the extra work everywhere rather than only
		///    where needed. It still places a burden on the user (increasing the global clamp appropriately),
		///    but not quite such a bad one as they don't have to figure anything out and only have one number to set.
		///
		/// 3) Have the StandardOptions node secretly compute a global "maxSegments" behind the scenes. This would
		///    work as for 2) but remove the burden from the user. However, it would mean preventing any expressions
		///    or connections being used on the segments attributes, because they could be used to cheat the system.
		///    It could potentially be faster than 2) because it wouldn't have to do all nondivisible numbers - it
		///    could know exactly which numbers of segments were in existence. It still suffers from the
		///    "pay the price everywhere" problem.	
				
		std::set<float> times;
		motionTimes( ( m_options.deformationBlur && m_attributes.deformationBlur ) ? m_attributes.deformationBlurSegments : 0, times );
		motionTimes( ( m_options.transformBlur && m_attributes.transformBlur ) ? m_attributes.transformBlurSegments : 0, times );
				
		Box3f result;
		for( std::set<float>::const_iterator it = times.begin(), eIt = times.end(); it != eIt; it++ )
		{
			timeContext->setFrame( *it );
			Box3f b = m_scenePlug->boundPlug()->getValue();
			M44f t = m_scenePlug->transformPlug()->getValue();
			result.extendBy( transform( b, t ) );
		}
		
		return result;
	}
	catch( const std::exception &e )
	{
		IECore::msg( IECore::Msg::Error, "SceneProcedural::bound()", e.what() );
	}
	return Box3f();
}
Exemple #3
0
void ExecutableNode::executeSequence( const std::vector<float> &frames ) const
{
	ContextPtr context = new Context( *Context::current(), Context::Borrowed );
	Context::Scope scopedContext( context.get() );

	for ( std::vector<float>::const_iterator it = frames.begin(); it != frames.end(); ++it )
	{
		context->setFrame( *it );
		execute();
	}
}
Exemple #4
0
void SceneWriter::executeSequence( const std::vector<float> &frames ) const
{
	const ScenePlug *scene = inPlug()->getInput<ScenePlug>();
	if( !scene )
	{
		throw IECore::Exception( "No input scene" );
	}

	ContextPtr context = new Context( *Context::current(), Context::Borrowed );
	Context::Scope scopedContext( context.get() );

	std::string fileName = context->substitute( fileNamePlug()->getValue() );
	createDirectories( fileName );
	SceneInterfacePtr output = SceneInterface::create( fileName, IndexedIO::Write );

	for ( std::vector<float>::const_iterator it = frames.begin(); it != frames.end(); ++it )
	{
		context->setFrame( *it );
		double time = *it / g_frameRate;
		writeLocation( scene, ScenePlug::ScenePath(), context.get(), output.get(), time );
	}
}
Exemple #5
0
bool ImageReader::computeFrameMask( const Context *context, ContextPtr &maskedContext ) const
{
    int frameStartMask = startFramePlug()->getValue();
    int frameEndMask = endFramePlug()->getValue();
    FrameMaskMode frameStartMaskMode = (FrameMaskMode)startModePlug()->getValue();
    FrameMaskMode frameEndMaskMode = (FrameMaskMode)endModePlug()->getValue();

    int origFrame = (int)context->getFrame();
    int maskedFrame = std::min( frameEndMask, std::max( frameStartMask, origFrame ) );

    if( origFrame == maskedFrame )
    {
        // no need for anything special when
        // we're within the mask range.
        return false;
    }

    FrameMaskMode maskMode = ( origFrame < maskedFrame ) ? frameStartMaskMode : frameEndMaskMode;

    if( maskMode == None )
    {
        // no need for anything special when
        // we're in FrameMaskMode::None
        return false;
    }

    // we need to create the masked context
    // for both BlackOutSide and ClampToFrame,
    // because some plugs require valid data
    // from the mask range even in either way.

    maskedContext = new Gaffer::Context( *context, Context::Borrowed );
    maskedContext->setFrame( maskedFrame );

    return ( maskMode == BlackOutside );
}
Exemple #6
0
void Dispatcher::dispatch( const std::vector<NodePtr> &nodes ) const
{
	// clear job directory, so that if our node validation fails,
	// jobDirectory() won't return the result from the previous dispatch.
	m_jobDirectory = "";

	// validate the nodes we've been given

	if ( nodes.empty() )
	{
		throw IECore::Exception( getName().string() + ": Must specify at least one node to dispatch." );
	}

	std::vector<ExecutableNodePtr> executables;
	const ScriptNode *script = (*nodes.begin())->scriptNode();
	for ( std::vector<NodePtr>::const_iterator nIt = nodes.begin(); nIt != nodes.end(); ++nIt )
	{
		const ScriptNode *currentScript = (*nIt)->scriptNode();
		if ( !currentScript || currentScript != script )
		{
			throw IECore::Exception( getName().string() + ": Dispatched nodes must all belong to the same ScriptNode." );
		}

		if ( ExecutableNode *executable = runTimeCast<ExecutableNode>( nIt->get() ) )
		{
			executables.push_back( executable );
		}
		else if ( const Box *box = runTimeCast<const Box>( nIt->get() ) )
		{
			for ( RecursiveOutputPlugIterator plugIt( box ); plugIt != plugIt.end(); ++plugIt )
			{
				Node *sourceNode = plugIt->get()->source<Plug>()->node();
				if ( ExecutableNode *executable = runTimeCast<ExecutableNode>( sourceNode ) )
				{
					executables.push_back( executable );
				}
			}
		}
		else
		{
			throw IECore::Exception( getName().string() + ": Dispatched nodes must be ExecutableNodes or Boxes containing ExecutableNodes." );
		}
	}

	// create the job directory now, so it's available in preDispatchSignal().
	const Context *context = Context::current();
	m_jobDirectory = createJobDirectory( context );

	// this object calls this->preDispatchSignal() in its constructor and this->postDispatchSignal()
	// in its destructor, thereby guaranteeing that we always call this->postDispatchSignal().

	DispatcherSignalGuard signalGuard( this, executables );
	if ( signalGuard.cancelledByPreDispatch() )
	{
		return;
	}

	std::vector<FrameList::Frame> frames;
	FrameListPtr frameList = frameRange( script, context );
	frameList->asList( frames );

	size_t i = 0;
	ExecutableNode::Tasks tasks;
	tasks.reserve( executables.size() * frames.size() );
	for ( std::vector<FrameList::Frame>::const_iterator fIt = frames.begin(); fIt != frames.end(); ++fIt )
	{
		for ( std::vector<ExecutableNodePtr>::const_iterator nIt = executables.begin(); nIt != executables.end(); ++nIt, ++i )
		{
			ContextPtr frameContext = new Context( *context, Context::Borrowed );
			frameContext->setFrame( *fIt );
			tasks.push_back( ExecutableNode::Task( *nIt, frameContext ) );
		}
	}

	TaskBatchPtr rootBatch = batchTasks( tasks );

	if ( !rootBatch->requirements().empty() )
	{
		doDispatch( rootBatch.get() );
	}

	// inform the guard that the process has been completed, so it can pass this info to
	// postDispatchSignal():

	signalGuard.success();
}
Exemple #7
0
void SceneProcedural::render( RendererPtr renderer ) const
{	
	Context::Scope scopedContext( m_context );
	
	/// \todo See above.
	try
	{
	
		// get all the attributes, and early out if we're not visibile
	
		ConstCompoundObjectPtr attributes = m_scenePlug->attributesPlug()->getValue();
		const BoolData *visibilityData = attributes->member<BoolData>( "gaffer:visibility" );
		if( visibilityData && !visibilityData->readable() )
		{
			return;
		}

		// if we are visible then make an attribute block to contain everything, set the name
		// and get on with generating things.

		AttributeBlock attributeBlock( renderer );

		std::string name = "";
		for( ScenePlug::ScenePath::const_iterator it = m_scenePath.begin(), eIt = m_scenePath.end(); it != eIt; it++ )
		{
			name += "/" + it->string();
		}
		renderer->setAttribute( "name", new StringData( name ) );

		// transform
		
		std::set<float> transformTimes;
		motionTimes( ( m_options.transformBlur && m_attributes.transformBlur ) ? m_attributes.transformBlurSegments : 0, transformTimes );
		{
			ContextPtr timeContext = new Context( *m_context );
			Context::Scope scopedTimeContext( timeContext );
			
			MotionBlock motionBlock( renderer, transformTimes, transformTimes.size() > 1 );
			
			for( std::set<float>::const_iterator it = transformTimes.begin(), eIt = transformTimes.end(); it != eIt; it++ )
			{
				timeContext->setFrame( *it );
				renderer->concatTransform( m_scenePlug->transformPlug()->getValue() );
			}
		}
		
		// attributes
		
		for( CompoundObject::ObjectMap::const_iterator it = attributes->members().begin(), eIt = attributes->members().end(); it != eIt; it++ )
		{
			if( const StateRenderable *s = runTimeCast<const StateRenderable>( it->second.get() ) )
			{
				s->render( renderer );
			}
			else if( const ObjectVector *o = runTimeCast<const ObjectVector>( it->second.get() ) )
			{
				for( ObjectVector::MemberContainer::const_iterator it = o->members().begin(), eIt = o->members().end(); it != eIt; it++ )
				{
					const StateRenderable *s = runTimeCast<const StateRenderable>( it->get() );
					if( s )
					{
						s->render( renderer );
					}
				}
			}
			else if( const Data *d = runTimeCast<const Data>( it->second.get() ) )
			{
				renderer->setAttribute( it->first, d );
			}
		}
		
		// object
		
		std::set<float> deformationTimes;
		motionTimes( ( m_options.deformationBlur && m_attributes.deformationBlur ) ? m_attributes.deformationBlurSegments : 0, deformationTimes );
		{
			ContextPtr timeContext = new Context( *m_context );
			Context::Scope scopedTimeContext( timeContext );
		
			unsigned timeIndex = 0;
			for( std::set<float>::const_iterator it = deformationTimes.begin(), eIt = deformationTimes.end(); it != eIt; it++, timeIndex++ )
			{
				timeContext->setFrame( *it );
				ConstObjectPtr object = m_scenePlug->objectPlug()->getValue();
				if( const Primitive *primitive = runTimeCast<const Primitive>( object.get() ) )
				{
					if( deformationTimes.size() > 1 && timeIndex == 0 )
					{
						renderer->motionBegin( deformationTimes );
					}
						
						primitive->render( renderer );
					
					if( deformationTimes.size() > 1 && timeIndex == deformationTimes.size() - 1 )
					{
						renderer->motionEnd();
					}
				}
				else if( const Camera *camera = runTimeCast<const Camera>( object.get() ) )
				{
					/// \todo This absolutely does not belong here, but until we have
					/// a mechanism for drawing manipulators, we don't have any other
					/// means of visualising the cameras.
					if( renderer->isInstanceOf( "IECoreGL::Renderer" ) )
					{
						drawCamera( camera, renderer.get() );
					}
					break; // no motion blur for these chappies.
				}
				else if( const Light *light = runTimeCast<const Light>( object.get() ) )
				{
					/// \todo This doesn't belong here.
					if( renderer->isInstanceOf( "IECoreGL::Renderer" ) )
					{
						drawLight( light, renderer.get() );
					}
					break; // no motion blur for these chappies.
				}
				else if( const VisibleRenderable* renderable = runTimeCast< const VisibleRenderable >( object.get() ) )
				{
					renderable->render( renderer );
					break; // no motion blur for these chappies.
				}
			
			}
		}
	
		// children

		ConstInternedStringVectorDataPtr childNames = m_scenePlug->childNamesPlug()->getValue();
		if( childNames->readable().size() )
		{		
			bool expand = true;
			if( m_pathsToExpand )
			{
				expand = m_pathsToExpand->readable().match( m_scenePath ) & Filter::ExactMatch;
			}
			
			if( !expand )
			{
				renderer->setAttribute( "gl:primitive:wireframe", new BoolData( true ) );
				renderer->setAttribute( "gl:primitive:solid", new BoolData( false ) );
				renderer->setAttribute( "gl:curvesPrimitive:useGLLines", new BoolData( true ) );
				Box3f b = m_scenePlug->boundPlug()->getValue();
				CurvesPrimitive::createBox( b )->render( renderer );	
			}
			else
			{
				ScenePlug::ScenePath childScenePath = m_scenePath;
				childScenePath.push_back( InternedString() ); // for the child name
				for( vector<InternedString>::const_iterator it=childNames->readable().begin(); it!=childNames->readable().end(); it++ )
				{
					childScenePath[m_scenePath.size()] = *it;
					renderer->setAttribute( "name", new StringData( *it ) );
					renderer->procedural( new SceneProcedural( *this, childScenePath ) );
				}
			}	
		}
	}
	catch( const std::exception &e )
	{
		IECore::msg( IECore::Msg::Error, "SceneProcedural::render()", e.what() );
	}	
}
void SceneProcedural::render( Renderer *renderer ) const
{
	tbb::task_scheduler_init tsi( tbb::task_scheduler_init::deferred );
	initializeTaskScheduler( tsi );

	Context::Scope scopedContext( m_context.get() );

	std::string name;
	ScenePlug::pathToString( m_scenePath, name );

	/// \todo See above.
	try
	{

		// get all the attributes, and early out if we're not visibile

		const BoolData *visibilityData = m_attributesObject->member<BoolData>( g_visibleAttributeName );
		if( visibilityData && !visibilityData->readable() )
		{

			if( !m_rendered )
			{
				decrementPendingProcedurals();
			}
			m_rendered = true;
			return;
		}

		// if we are visible then make an attribute block to contain everything, set the name
		// and get on with generating things.

		AttributeBlock attributeBlock( renderer );

		renderer->setAttribute( "name", new StringData( name ) );

		// transform

		std::set<float> transformTimes;
		motionTimes( ( m_options.transformBlur && m_attributes.transformBlur ) ? m_attributes.transformBlurSegments : 0, transformTimes );
		{
			ContextPtr timeContext = new Context( *m_context, Context::Borrowed );
			Context::Scope scopedTimeContext( timeContext.get() );

			MotionBlock motionBlock( renderer, transformTimes, transformTimes.size() > 1 );

			for( std::set<float>::const_iterator it = transformTimes.begin(), eIt = transformTimes.end(); it != eIt; it++ )
			{
				timeContext->setFrame( *it );
				renderer->concatTransform( m_scenePlug->transformPlug()->getValue() );
			}
		}

		// attributes

		outputAttributes( m_attributesObject.get(), renderer );

		// object

		std::set<float> deformationTimes;
		motionTimes( ( m_options.deformationBlur && m_attributes.deformationBlur ) ? m_attributes.deformationBlurSegments : 0, deformationTimes );
		{
			ContextPtr timeContext = new Context( *m_context, Context::Borrowed );
			Context::Scope scopedTimeContext( timeContext.get() );

			unsigned timeIndex = 0;
			for( std::set<float>::const_iterator it = deformationTimes.begin(), eIt = deformationTimes.end(); it != eIt; it++, timeIndex++ )
			{
				timeContext->setFrame( *it );
				ConstObjectPtr object = m_scenePlug->objectPlug()->getValue();
				if( const Primitive *primitive = runTimeCast<const Primitive>( object.get() ) )
				{
					if( deformationTimes.size() > 1 && timeIndex == 0 )
					{
						renderer->motionBegin( deformationTimes );
					}

						primitive->render( renderer );

					if( deformationTimes.size() > 1 && timeIndex == deformationTimes.size() - 1 )
					{
						renderer->motionEnd();
					}
				}
				else if( const VisibleRenderable* renderable = runTimeCast< const VisibleRenderable >( object.get() ) )
				{
					renderable->render( renderer );
					break; // no motion blur for these chappies.
				}

			}
		}

		// children

		ConstInternedStringVectorDataPtr childNames = m_scenePlug->childNamesPlug()->getValue();
		if( childNames->readable().size() )
		{
			// Creating a SceneProcedural involves an attribute/bound evaluation, which are
			// potentially expensive, so we're parallelizing them.

			// allocate space for child procedurals:
			SceneProceduralCreate::SceneProceduralContainer childProcedurals( childNames->readable().size() );

			// create procedurals in parallel:
			SceneProceduralCreate s(
				childProcedurals,
				*this,
				childNames->readable()
			);
			tbb::parallel_for( tbb::blocked_range<int>( 0, childNames->readable().size() ), s );

			// send to the renderer in series:

			std::vector<SceneProceduralPtr>::const_iterator procIt = childProcedurals.begin(), procEit = childProcedurals.end();
			for( ; procIt != procEit; ++procIt )
			{
				renderer->procedural( *procIt );
			}
		}
	}
	catch( const std::exception &e )
	{
		IECore::msg( IECore::Msg::Error, "SceneProcedural::render() " + name, e.what() );
	}
	if( !m_rendered )
	{
		decrementPendingProcedurals();
	}
	m_rendered = true;
}
Exemple #9
0
void Dispatcher::dispatch( const std::vector<NodePtr> &nodes ) const
{
    // clear job directory, so that if our node validation fails,
    // jobDirectory() won't return the result from the previous dispatch.
    m_jobDirectory = "";

    // validate the nodes we've been given

    if ( nodes.empty() )
    {
        throw IECore::Exception( getName().string() + ": Must specify at least one node to dispatch." );
    }

    std::vector<TaskNodePtr> taskNodes;
    const ScriptNode *script = (*nodes.begin())->scriptNode();
    for ( std::vector<NodePtr>::const_iterator nIt = nodes.begin(); nIt != nodes.end(); ++nIt )
    {
        const ScriptNode *currentScript = (*nIt)->scriptNode();
        if ( !currentScript || currentScript != script )
        {
            throw IECore::Exception( getName().string() + ": Dispatched nodes must all belong to the same ScriptNode." );
        }

        if ( TaskNode *taskNode = runTimeCast<TaskNode>( nIt->get() ) )
        {
            taskNodes.push_back( taskNode );
        }
        else if ( const SubGraph *subGraph = runTimeCast<const SubGraph>( nIt->get() ) )
        {
            for ( RecursiveOutputPlugIterator plugIt( subGraph ); !plugIt.done(); ++plugIt )
            {
                Node *sourceNode = plugIt->get()->source<Plug>()->node();
                if ( TaskNode *taskNode = runTimeCast<TaskNode>( sourceNode ) )
                {
                    taskNodes.push_back( taskNode );
                }
            }
        }
        else
        {
            throw IECore::Exception( getName().string() + ": Dispatched nodes must be TaskNodes or SubGraphs containing TaskNodes." );
        }
    }

    // create the job directory now, so it's available in preDispatchSignal().
    ContextPtr context = new Context( *Context::current(), Context::Borrowed );
    m_jobDirectory = createJobDirectory( context.get() );
    context->set( g_jobDirectoryContextEntry, m_jobDirectory );

    // this object calls this->preDispatchSignal() in its constructor and this->postDispatchSignal()
    // in its destructor, thereby guaranteeing that we always call this->postDispatchSignal().

    DispatcherSignalGuard signalGuard( this, taskNodes );
    if ( signalGuard.cancelledByPreDispatch() )
    {
        return;
    }

    std::vector<FrameList::Frame> frames;
    FrameListPtr frameList = frameRange( script, context.get() );
    frameList->asList( frames );

    Batcher batcher;
    for( std::vector<FrameList::Frame>::const_iterator fIt = frames.begin(); fIt != frames.end(); ++fIt )
    {
        for( std::vector<TaskNodePtr>::const_iterator nIt = taskNodes.begin(); nIt != taskNodes.end(); ++nIt )
        {
            context->setFrame( *fIt );
            batcher.addTask( TaskNode::Task( *nIt, context.get() ) );
        }
    }

    executeAndPruneImmediateBatches( batcher.rootBatch() );

    if( !batcher.rootBatch()->preTasks().empty() )
    {
        doDispatch( batcher.rootBatch() );
    }

    // inform the guard that the process has been completed, so it can pass this info to
    // postDispatchSignal():

    signalGuard.success();
}
Exemple #10
0
void Dispatcher::dispatch( const std::vector<NodePtr> &nodes ) const
{
	if ( nodes.empty() )
	{
		throw IECore::Exception( getName().string() + ": Must specify at least one node to dispatch." );
	}

	std::vector<ExecutableNodePtr> executables;
	const ScriptNode *script = (*nodes.begin())->scriptNode();
	for ( std::vector<NodePtr>::const_iterator nIt = nodes.begin(); nIt != nodes.end(); ++nIt )
	{
		const ScriptNode *currentScript = (*nIt)->scriptNode();
		if ( !currentScript || currentScript != script )
		{
			throw IECore::Exception( getName().string() + ": Dispatched nodes must all belong to the same ScriptNode." );
		}

		if ( ExecutableNode *executable = runTimeCast<ExecutableNode>( nIt->get() ) )
		{
			executables.push_back( executable );
		}
		else if ( const Box *box = runTimeCast<const Box>( nIt->get() ) )
		{
			for ( RecursiveOutputPlugIterator plugIt( box ); plugIt != plugIt.end(); ++plugIt )
			{
				Node *sourceNode = plugIt->get()->source<Plug>()->node();
				if ( ExecutableNode *executable = runTimeCast<ExecutableNode>( sourceNode ) )
				{
					executables.push_back( executable );
				}
			}
		}
		else
		{
			throw IECore::Exception( getName().string() + ": Dispatched nodes must be ExecutableNodes or Boxes containing ExecutableNodes." );
		}
	}

	if ( preDispatchSignal()( this, executables ) )
	{
		/// \todo: communicate the cancellation to the user
		return;
	}

	const Context *context = Context::current();

	std::vector<FrameList::Frame> frames;
	FrameListPtr frameList = frameRange( script, context );
	frameList->asList( frames );

	size_t i = 0;
	ExecutableNode::Tasks tasks;
	tasks.reserve( executables.size() * frames.size() );
	for ( std::vector<FrameList::Frame>::const_iterator fIt = frames.begin(); fIt != frames.end(); ++fIt )
	{
		for ( std::vector<ExecutableNodePtr>::const_iterator nIt = executables.begin(); nIt != executables.end(); ++nIt, ++i )
		{
			ContextPtr frameContext = new Context( *context, Context::Borrowed );
			frameContext->setFrame( *fIt );
			tasks.push_back( ExecutableNode::Task( *nIt, frameContext ) );
		}
	}

	TaskBatchPtr rootBatch = batchTasks( tasks );

	if ( !rootBatch->requirements().empty() )
	{
		doDispatch( rootBatch.get() );
	}

	postDispatchSignal()( this, executables );
}