Skip to content

Commit 82e1e9c

Browse files
committed
Merge branch '1.4_maintenance'
2 parents c9b577f + 0dd8486 commit 82e1e9c

File tree

7 files changed

+174
-40
lines changed

7 files changed

+174
-40
lines changed

Changes.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,28 @@ Breaking Changes
7070
- Loop : Removed `nextIterationContext()` method.
7171
- NodeGadget, ConnectionGadget : Removed `activeForFocusNode()` virtual methods. Override `updateFromContextTracker()` instead.
7272

73-
1.4.x.x (relative to 1.4.10.0)
73+
1.4.x.x (relative to 1.4.11.0)
7474
=======
7575

7676
Improvements
7777
------------
7878

79+
- UI Editor :
80+
- Added the ability to edit the scale of node icons.
81+
- Improved layout of Box node plug creator visibility toggles.
82+
- ArnoldShader : Moved the `toon` shader's `*_tonemap_hue_saturation` parameters to appropriate sections in the UI.
83+
84+
API
85+
---
86+
87+
- MetadataWidget : Added `NumericMetadataWidget` class.
88+
89+
1.4.11.0 (relative to 1.4.10.0)
90+
========
91+
92+
Improvements
93+
------------
94+
7995
- SetExpressions : Set Expressions containing only whitespace characters are now treated as empty rather than producing an error.
8096
- ArnoldShader :
8197
- Added a UI layout for the new `openpbr_surface` shader.
@@ -86,6 +102,7 @@ Improvements
86102
- Blue : Proxy
87103
- Red : Guide
88104
- Catalogue : Added a handle for controlling the relative sizes of the listing and image property widgets.
105+
- RenderPassEditor, LightEditor : Improved update performance for certain graph configurations, by optimising `SceneAlgo::history()` (#5199).
89106

90107
Fixes
91108
-----
@@ -98,6 +115,7 @@ Fixes
98115
- Fixed bug which allowed locked Catalogues to be edited.
99116
- Fixed NodeEditor update when the first image is added or the last image is removed.
100117
- NameWidget : Fixed bug which allowed plugs on locked nodes to be renamed.
118+
- ValuePlug : Fixed the plug passed to `Monitor::forceMonitoring()`. Previously `Process::destinationPlug()` was being passed instead of `Process::plug()`.
101119

102120
API
103121
---

arnoldPlugins/gaffer.mtd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3035,6 +3035,10 @@
30353035
gaffer.graphEditorLayout.visible BOOL true
30363036
page STRING "Edge"
30373037
gaffer.layout.activator STRING "edgeEnabled"
3038+
[attr edge_tonemap_hue_saturation]
3039+
page STRING "Edge"
3040+
label STRING "Tonemap Hue Saturation"
3041+
gaffer.layout.activator STRING "edgeEnabled"
30383042
[attr edge_opacity]
30393043
page STRING "Edge"
30403044
gaffer.layout.activator STRING "edgeEnabled"
@@ -3083,6 +3087,10 @@
30833087
gaffer.graphEditorLayout.visible BOOL true
30843088
page STRING "Silhouette"
30853089
gaffer.layout.activator STRING "silhouetteEnabled"
3090+
[attr silhouette_tonemap_hue_saturation]
3091+
page STRING "Silhouette"
3092+
label STRING "Tonemap Hue Saturation"
3093+
gaffer.layout.activator STRING "silhouetteEnabled"
30863094
[attr silhouette_opacity]
30873095
page STRING "Silhouette"
30883096
gaffer.layout.activator STRING "silhouetteEnabled"
@@ -3099,6 +3107,9 @@
30993107
[attr base_tonemap]
31003108
gaffer.graphEditorLayout.visible BOOL true
31013109
page STRING "Base"
3110+
[attr base_tonemap_hue_saturation]
3111+
page STRING "Base"
3112+
label STRING "Tonemap Hue Saturation"
31023113

31033114
[attr specular]
31043115
page STRING "Specular"
@@ -3112,6 +3123,9 @@
31123123
page STRING "Specular"
31133124
[attr specular_tonemap]
31143125
page STRING "Specular"
3126+
[attr specular_tonemap_hue_saturation]
3127+
page STRING "Specular"
3128+
label STRING "Tonemap Hue Saturation"
31153129

31163130
[attr lights]
31173131
page STRING "Stylized Highlight"

python/GafferSceneTest/SceneAlgoTest.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,6 +1946,46 @@ def assertNoCanceller( history ) :
19461946

19471947
assertNoCanceller( history )
19481948

1949+
@GafferTest.TestRunner.PerformanceTestMethod()
1950+
def testHistoryDiamondPerformance( self ) :
1951+
1952+
# A series of diamonds where every iteration reads the output of the
1953+
# previous iteration twice and then feeds into the next iteration.
1954+
#
1955+
# o
1956+
# / \
1957+
# o o
1958+
# \ /
1959+
# o
1960+
# / \
1961+
# o o
1962+
# \ /
1963+
# o
1964+
# / \
1965+
# ...
1966+
# \ /
1967+
# o
1968+
#
1969+
# Without caching, this leads to an explosion of paths through the
1970+
# graph, and can lead to poor performance in `history()`.
1971+
1972+
plane = GafferScene.Plane()
1973+
1974+
loop = Gaffer.Loop()
1975+
loop.setup( plane["out"] )
1976+
loop["in"].setInput( plane["out"] )
1977+
1978+
copyOptions = GafferScene.CopyOptions()
1979+
copyOptions["in"].setInput( loop["previous"] )
1980+
copyOptions["source"].setInput( loop["previous"] )
1981+
copyOptions["options"].setValue( "*" )
1982+
1983+
loop["next"].setInput( copyOptions["out"] )
1984+
loop["iterations"].setValue( 20 )
1985+
1986+
with GafferTest.TestRunner.PerformanceScope() :
1987+
GafferScene.SceneAlgo.history( loop["out"]["globals"] )
1988+
19491989
def testLinkingQueries( self ) :
19501990

19511991
# Everything linked to `defaultLights` via the default value for the attribute.

python/GafferUI/MetadataWidget.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ def _updateFromValue( self, value ) :
109109

110110
## Must be called by derived classes to update
111111
# the Metadata value when the widget value changes.
112-
def _updateFromWidget( self, value ) :
112+
def _updateFromWidget( self, value, mergeGroup = "" ) :
113113

114114
if self.__target is None :
115115
return
116116

117-
with Gaffer.UndoScope( self.__target.ancestor( Gaffer.ScriptNode ) ) :
117+
with Gaffer.UndoScope( self.__target.ancestor( Gaffer.ScriptNode ), mergeGroup = mergeGroup ) :
118118
Gaffer.Metadata.registerValue( self.__target, self.__key, value )
119119

120120
## May be called by derived classes to deregister the
@@ -347,3 +347,43 @@ def __buttonClicked( self, widget ) :
347347
if chosenPath is not None :
348348
self.__path.setFromString( str( chosenPath ) )
349349
self.__editingFinished()
350+
351+
class NumericMetadataWidget( MetadataWidget ) :
352+
353+
def __init__( self, key, target = None, defaultValue = 0, **kw ) :
354+
355+
self.__numericWidget = GafferUI.NumericWidget( value = defaultValue )
356+
357+
self.__defaultValue = defaultValue
358+
# we use these to decide which actions to merge into a single undo
359+
self.__lastChangedReason = None
360+
self.__mergeGroupId = 0
361+
362+
MetadataWidget.__init__( self, self.__numericWidget, key, target, defaultValue = defaultValue, **kw )
363+
364+
self.__numericWidget.valueChangedSignal().connect(
365+
Gaffer.WeakMethod( self.__valueChanged ), scoped = False
366+
)
367+
368+
def numericWidget( self ) :
369+
370+
return self.__numericWidget
371+
372+
def _updateFromValue( self, value ) :
373+
374+
self.__numericWidget.setValue( type( self.__defaultValue )( value ) )
375+
376+
def __valueChanged( self, widget, reason ) :
377+
378+
if reason == GafferUI.NumericWidget.ValueChangedReason.InvalidEdit :
379+
self._updateFromValue( self.defaultValue() )
380+
return
381+
382+
if not widget.changesShouldBeMerged( self.__lastChangedReason, reason ) :
383+
self.__mergeGroupId += 1
384+
self.__lastChangedReason = reason
385+
386+
self._updateFromWidget(
387+
self.__numericWidget.getValue(),
388+
mergeGroup = "NumericMetadataWidget{}{}".format( id( self, ), self.__mergeGroupId )
389+
)

python/GafferUI/UIEditor.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,18 @@ def __init__( self, scriptNode, **kw ) :
111111
MetadataWidget.FileSystemPathMetadataWidget( key = "icon" )
112112
)
113113

114+
GafferUI.Label( "Scale" )
115+
116+
scaleWidget = MetadataWidget.NumericMetadataWidget( key = "iconScale", defaultValue = 1.5 )
117+
scaleWidget.numericWidget()._qtWidget().setMaximumWidth( 60 )
118+
self.__nodeMetadataWidgets.append( scaleWidget )
119+
114120
with _Row() as self.__plugAddButtons :
115121

116122
_Label( "Plug Creators" )
117123

118124
for side in ( "Top", "Bottom", "Left", "Right" ) :
119-
_Label( side )._qtWidget().setFixedWidth( 40 )
125+
GafferUI.Label( side )
120126
self.__nodeMetadataWidgets.append( MetadataWidget.BoolMetadataWidget(
121127
key = "noduleLayout:customGadget:addButton%s:visible" % side,
122128
defaultValue = True
@@ -400,7 +406,7 @@ class _Row( GafferUI.ListContainer ) :
400406

401407
def __init__( self, *args, **kw ) :
402408

403-
GafferUI.ListContainer.__init__( self, GafferUI.ListContainer.Orientation.Horizontal, spacing = 4, *args, **kw )
409+
GafferUI.ListContainer.__init__( self, GafferUI.ListContainer.Orientation.Horizontal, spacing = 8, *args, **kw )
404410

405411
##########################################################################
406412
# Hierarchical representation of a plug layout, suitable for manipulating

src/Gaffer/ValuePlug.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ class ValuePlug::HashProcess : public Process
241241
// Then get our hash. We do this using this `acquireHash()` functor so that
242242
// we can repeat the process for `Checked` mode.
243243

244-
const bool forceMonitoring = Process::forceMonitoring( threadState, plug, staticType );
244+
const bool forceMonitoring = Process::forceMonitoring( threadState, p, staticType );
245245

246246
auto acquireHash = [&]( const HashCacheKey &cacheKey ) {
247247

src/GafferScene/SceneAlgo.cpp

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,6 @@ class CapturingMonitor : public Monitor
437437
{
438438
const Plug *p = process->plug();
439439

440-
CapturedProcess::Ptr capturedProcess;
441-
ProcessOrScope entry;
442440
if( !shouldCapture( p ) )
443441
{
444442
// Parents may spawn other processes in support of the requested plug. This is one
@@ -450,39 +448,44 @@ class CapturingMonitor : public Monitor
450448
// order of the stack is preserved - if this happens out of order, the stack will be
451449
// corrupted, and weird crashes will happen. But as long as it is created in
452450
// processStarted, and released in processFinished, everything should line up.
453-
entry = std::make_unique<Monitor::Scope>( this, false );
454-
}
455-
else
456-
{
457-
capturedProcess = std::make_unique<CapturedProcess>();
458-
capturedProcess->type = process->type();
459-
capturedProcess->plug = p;
460-
capturedProcess->destinationPlug = process->destinationPlug();
461-
capturedProcess->context = new Context( *process->context(), /* omitCanceller = */ true );
462-
entry = capturedProcess.get();
451+
Mutex::scoped_lock lock( m_mutex );
452+
m_processMap[process] = std::make_unique<Monitor::Scope>( this, false );
453+
return;
463454
}
464455

456+
// Capture this process.
457+
458+
CapturedProcess::Ptr capturedProcess = std::make_unique<CapturedProcess>();
459+
capturedProcess->type = process->type();
460+
capturedProcess->plug = p;
461+
capturedProcess->destinationPlug = process->destinationPlug();
462+
capturedProcess->context = new Context( *process->context(), /* omitCanceller = */ true );
463+
465464
Mutex::scoped_lock lock( m_mutex );
466-
m_processMap[process] = std::move( entry );
465+
m_processMap[process] = capturedProcess.get();
467466

468-
if( capturedProcess )
467+
ProcessMap::const_iterator it = m_processMap.find( process->parent() );
468+
if( it != m_processMap.end() )
469469
{
470-
ProcessMap::const_iterator it = m_processMap.find( process->parent() );
471-
if( it != m_processMap.end() )
470+
CapturedProcess * const * parent = std::get_if<CapturedProcess *>( &it->second );
471+
if( parent && *parent )
472472
{
473-
CapturedProcess * const * parent = std::get_if<CapturedProcess *>( &it->second );
474-
if( parent && *parent )
475-
{
476-
(*parent)->children.push_back( std::move( capturedProcess ) );
477-
}
478-
}
479-
else
480-
{
481-
// Either `process->parent()` was null, or was started
482-
// before we were made active via `Monitor::Scope`.
483-
m_rootProcesses.push_back( std::move( capturedProcess ) );
473+
(*parent)->children.push_back( std::move( capturedProcess ) );
484474
}
485475
}
476+
else
477+
{
478+
// Either `process->parent()` was null, or was started
479+
// before we were made active via `Monitor::Scope`.
480+
m_rootProcesses.push_back( std::move( capturedProcess ) );
481+
}
482+
483+
// Remember that we've monitored this process so that we dont force
484+
// its monitoring again.
485+
486+
IECore::MurmurHash h = process->context()->hash();
487+
h.append( reinterpret_cast<intptr_t>( p ) );
488+
m_monitoredSet.insert( h );
486489
}
487490

488491
void processFinished( const Process *process ) override
@@ -498,12 +501,22 @@ class CapturingMonitor : public Monitor
498501

499502
bool forceMonitoring( const Plug *plug, const IECore::InternedString &processType ) override
500503
{
501-
if( processType == g_hashProcessType && shouldCapture( plug ) )
504+
if( processType != g_hashProcessType || !shouldCapture( plug ) )
502505
{
503-
return true;
506+
return false;
504507
}
505508

506-
return false;
509+
// Don't force the monitoring of a process we've monitored already. This does
510+
// mean we throw away diamond dependencies in the process graph, but it is essential
511+
// for performance in some cases - see `testHistoryDiamondPerformance()` for example.
512+
/// \todo Potentially we could use the hash to find the previously captured process,
513+
/// and instance that into our process graph. This would require clients of `History`
514+
/// to be updated to handle such topologies efficiently by tracking previously visited
515+
/// items. It may also be of fairly low value, since typically our goal is to find the
516+
/// first relevant path through the graph to present to the user.
517+
IECore::MurmurHash h = Context::current()->hash();
518+
h.append( reinterpret_cast<intptr_t>( plug ) );
519+
return !m_monitoredSet.count( h );
507520
}
508521

509522
private :
@@ -518,15 +531,18 @@ class CapturingMonitor : public Monitor
518531
}
519532

520533
using Mutex = tbb::spin_mutex;
521-
522-
Mutex m_mutex;
523-
524534
using ProcessOrScope = std::variant<CapturedProcess *, std::unique_ptr<Monitor::Scope>>;
525535
using ProcessMap = boost::unordered_map<const Process *, ProcessOrScope>;
526536

537+
const IECore::InternedString m_scenePlugChildName;
538+
539+
// Protects `m_processMap` and `m_rootProcesses`.
540+
/// \todo Perhaps they should be concurrent containers instead?
541+
Mutex m_mutex;
527542
ProcessMap m_processMap;
528543
CapturedProcess::PtrVector m_rootProcesses;
529-
IECore::InternedString m_scenePlugChildName;
544+
545+
tbb::concurrent_unordered_set<IECore::MurmurHash> m_monitoredSet;
530546

531547
};
532548

0 commit comments

Comments
 (0)