Skip to content

Commit 3b6240a

Browse files
Merge pull request #6768 from GafferHQ/renderManCropFixes
RenderMan crop window fixes
2 parents b10cb1a + c6f4bc2 commit 3b6240a

File tree

13 files changed

+223
-70
lines changed

13 files changed

+223
-70
lines changed

Changes.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,19 @@ Fixes
1414

1515
- NodeEditor : Fixed "Revert to Defaults" to handle ganged plugs, and other plugs where a subset of children have input connections. In this case, the subset without inputs now revert correctly to their default values.
1616
- ShaderTweaks : Fixed context handling in "From Affected" and "From Selected" menu items.
17-
- RenderMan : Fixed `R10043 {WARNING} inputMaterial, unknown or mismatched input parameter of PxrSurface`.
1817
- SceneTestCase, ImageTestCase : Sanitisers are no longer installed when testing performance, since they add additional overhead.
18+
- RenderMan :
19+
- Fixed `R10043 {WARNING} inputMaterial, unknown or mismatched input parameter of PxrSurface`.
20+
- Fixed offset when reducing crop window size in RIS (#6727).
21+
- Fixed unwanted creation of new Catalogue images from InteractiveRenders in the following situations :
22+
- Changing camera.
23+
- Changing pixel filter or filter width.
24+
- Enlarging the crop region when rendering with RIS.
25+
- Changing the resolution.
26+
- Adding or removing outputs.
27+
- Added workaround for RenderMan interactive denoiser bugs :
28+
- Data window not updating when the crop window is edited.
29+
- Crashes when the crop window is edited.
1930

2031
API
2132
---

include/GafferScene/InteractiveRender.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ class GAFFERSCENE_API InteractiveRender : public Gaffer::ComputeNode
143143
IE_CORE_FORWARDDECLARE( RenderMessageHandler )
144144
RenderMessageHandlerPtr m_messageHandler;
145145

146+
friend class Catalogue;
147+
static bool renderIsActive( const std::string &renderId );
148+
146149
static size_t g_firstPlugIndex;
147150

148151
};

include/GafferScene/Private/RendererAlgo.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ struct GAFFERSCENE_API RenderOptions
9090
/// Creates the directories necessary to receive the outputs defined in globals.
9191
GAFFERSCENE_API void createOutputDirectories( const IECore::CompoundObject *globals );
9292

93+
// Returns the "gaffer:renderID" parameter added to outputs for the provided renderer.
94+
GAFFERSCENE_API std::string renderID( const IECoreScenePreview::Renderer *renderer );
9395

9496
// Returns whether the globals contain an Output that references the data `float id`
9597
GAFFERSCENE_API bool hasIDOutput( const IECore::CompoundObject *globals );
@@ -100,7 +102,6 @@ GAFFERSCENE_API bool hasInstanceIDOutput( const IECore::CompoundObject *globals
100102
// Returns a manifest file path if it's set in the globals, otherwise empty string
101103
GAFFERSCENE_API std::string renderManifestFilePath( const IECore::CompoundObject *globals );
102104

103-
104105
/// Sets `times` to a list of times to sample the transform or deformation of a
105106
/// location at, based on the render options and location attributes. Returns `true`
106107
/// if `times` was altered and `false` if it was already set correctly.

python/GafferArnoldTest/InteractiveArnoldRenderTest.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,9 @@ def testEditOutputType( self ) :
844844
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
845845
self.assertEqual( script["catalogue"]["out"].metadata()["gaffer:isRendering"], IECore.BoolData( True ) )
846846

847-
# Modify the output to render to file instead of the catalogue, and check
848-
# the catalogue image is closed and the file is created.
847+
# Modify the output to render to file instead of the catalogue. This isn't
848+
# particularly likely in practice, and would most likely indicate user error
849+
# of some sort. We expect the Catalogue to keep the image open.
849850

850851
with Gaffer.DirtyPropagationScope() :
851852

@@ -855,9 +856,30 @@ def testEditOutputType( self ) :
855856
self.uiThreadCallHandler.waitFor( 1 )
856857

857858
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
858-
self.assertNotIn( "gaffer:isRendering", script["catalogue"]["out"].metadata() )
859+
self.assertEqual( script["catalogue"]["out"].metadata()["gaffer:isRendering"], IECore.BoolData( True ) )
860+
self.assertTrue( ( self.temporaryDirectory() / "test.exr" ).is_file() )
861+
862+
# Switch back to rendering to the Catalogue, as if the user has fixed their error.
863+
864+
with Gaffer.DirtyPropagationScope() :
865+
866+
script["outputs"]["outputs"][0]["fileName"].setValue( "test" )
867+
script["outputs"]["outputs"][0]["type"].setValue( "ieDisplay" )
868+
869+
self.uiThreadCallHandler.waitFor( 1 )
870+
871+
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
872+
self.assertEqual( script["catalogue"]["out"].metadata()["gaffer:isRendering"], IECore.BoolData( True ) )
859873
self.assertTrue( ( self.temporaryDirectory() / "test.exr" ).is_file() )
860874

875+
# Stop the render, and check the image is closed.
876+
877+
script["renderer"]["state"].setValue( script["renderer"].State.Stopped )
878+
self.uiThreadCallHandler.waitFor( 1 )
879+
880+
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
881+
self.assertNotIn( "gaffer:isRendering", script["catalogue"]["out"].metadata() )
882+
861883
## \todo Promote to InteractiveRenderTest and check it works for other renderer backends.
862884
def testEditOutputFilterType( self ) :
863885

python/GafferRenderManTest/InteractiveRenderManRenderTest.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,20 @@ class InteractiveRenderManRenderTest( GafferSceneTest.InteractiveRenderTest ) :
5050

5151
renderer = "RenderMan"
5252

53-
def testEditCropWindow( self ) :
54-
55-
if self.renderer == "RenderMan" :
56-
# RIS doesn't reopen the display drivers when the crop
57-
# window decreases in size, only when it increases. This will
58-
# cause the base class test to fail, even though we are passing
59-
# edits and RenderMan is only re-rendering the requested area.
60-
## \todo Perhaps we can deal with this ourself by recreating
61-
# the RenderView to force reopening? Or modifying a single camera
62-
# instead of making a new one when the crop changes?
63-
self.skipTest( "Crop window doesn't change data window" )
64-
else :
65-
GafferSceneTest.InteractiveRenderTest.testEditCropWindow( self )
53+
def testEditCropWindowWithInteractiveDenoiser( self ) :
54+
55+
# See `_createOptions()`, where we use the test name to
56+
# enable the interactive denoiser.
57+
self.testEditCropWindow()
58+
59+
def _createOptions( self ) :
60+
61+
options = GafferRenderMan.RenderManOptions()
62+
if "WithInteractiveDenoiser" in self.id() :
63+
options["options"]["ri:interactiveDenoiser:enabled"]["enabled"].setValue( True )
64+
options["options"]["ri:interactiveDenoiser:enabled"]["value"].setValue( True )
65+
66+
return options
6667

6768
def _createConstantShader( self ) :
6869

python/GafferRenderManTest/RenderManRenderTest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,14 @@ def testRepeatedRender( self ) :
9191
s["render"]["renderer"].setValue( self.renderer )
9292
s["render"]["in"].setInput( s["outputs"]["out"] )
9393

94-
for i in range( 10 ) :
94+
## \todo This used to do 10 renders, but has been adjusted to avoid failures
95+
# later in CI. Bafflingly, what seems to be happening is that after a
96+
# certain number of renders, RenderMan's EXR driver generates bogus
97+
# channel names for a single render and then recovers. For CI to pass,
98+
# that broken render needs to not be one where the bug breaks one of our
99+
# assertions. We've reported this upstream, to hopefully we can revert
100+
# to 10 renders one day.
101+
for i in range( 5 ) :
95102

96103
s["render"]["task"].execute()
97104

python/GafferSceneTest/CatalogueTest.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,36 @@ def testYouOnlySaveOnce( self ) :
10711071
# made.
10721072
handler.assertDone()
10731073

1074+
def testUnfinishedRender( self ) :
1075+
1076+
# Send an image to a catalogue and "forget" to close the driver.
1077+
# The most likely cause of this would be all outputs accidentally being
1078+
# removed from an InteractiveRender. The image remains open (unsaved)
1079+
# in anticipation of the error being fixed.
1080+
1081+
script = Gaffer.ScriptNode()
1082+
1083+
script["catalogue"] = GafferScene.Catalogue()
1084+
1085+
script["constant"] = GafferImage.Constant()
1086+
script["constant"]["format"].setValue( GafferImage.Format( 100, 100 ) )
1087+
script["constant"]["color"].setValue( imath.Color4f( 1, 0, 0, 1 ) )
1088+
1089+
driver = self.sendImage( script["constant"]["out"], script["catalogue"], waitForSave = False, close = False )
1090+
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
1091+
self.assertEqual( script["catalogue"]["out"]["metadata"].getValue()[ self.__catalogueIsRenderingMetadataKey ].value, True )
1092+
1093+
# Save and load while the image is still unsaved. We expect the image
1094+
# to not be loaded, because it isn't backed by an image on disk.
1095+
1096+
script2 = Gaffer.ScriptNode()
1097+
script2.execute( script.serialise() )
1098+
self.assertEqual( len( script2["catalogue"]["images"] ), 0 )
1099+
1100+
# Clean up.
1101+
1102+
driver.close()
1103+
10741104
def testReorder( self ) :
10751105

10761106
for newOrder in [

python/GafferSceneTest/InteractiveRenderTest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ def testSwitchRenderCamera( self ) :
753753

754754
# `camera1` places the sphere in the centre of the image.
755755

756+
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
756757
self.assertAlmostEqual( self._color4fAtUV( script["catalogue"], imath.V2f( 0.5 ) ).a, 1, delta = 0.01 )
757758
self.assertAlmostEqual( self._color4fAtUV( script["catalogue"], imath.V2f( 0.99, 0.5 ) ).a, 0, delta = 0.01 )
758759

@@ -761,6 +762,7 @@ def testSwitchRenderCamera( self ) :
761762
script["options"]["options"]["render:camera"]["value"].setValue( "/group/camera2" )
762763
self.uiThreadCallHandler.waitFor( 1.0 )
763764

765+
self.assertEqual( len( script["catalogue"]["images"] ), 1 )
764766
self.assertAlmostEqual( self._color4fAtUV( script["catalogue"], imath.V2f( 0.99, 0.5 ) ).a, 1, delta = 0.01 )
765767
self.assertAlmostEqual( self._color4fAtUV( script["catalogue"], imath.V2f( 0.5 ) ).a, 0, delta = 0.01 )
766768

src/GafferScene/Catalogue.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,14 @@ class Catalogue::InternalImage : public ImageNode
417417
}
418418
}
419419

420+
if( InteractiveRender::renderIsActive( m_renderID ) )
421+
{
422+
// Render is still active. Assume drivers were just closed
423+
// in preparation for creating new drivers (with a new
424+
// data window or header data for example).
425+
return;
426+
}
427+
420428
// All our drivers have been closed, so the render has completed.
421429
// Save the image to disk. We do this in the background because
422430
// saving large images with many AOVs takes several seconds.

src/GafferScene/InteractiveRender.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ PendingUpdates &pendingUpdates()
8585

8686
const InternedString g_rendererOptionName( "option:render:defaultRenderer" );
8787

88+
unordered_set<string> &activeRenderIds()
89+
{
90+
static unordered_set<string> g_activeRenderIds;
91+
return g_activeRenderIds;
92+
}
93+
8894
} // anon namespace
8995

9096
// A thread-safe message handler for render messaging
@@ -362,6 +368,7 @@ void InteractiveRender::update()
362368
"",
363369
m_messageHandler.get()
364370
);
371+
activeRenderIds().insert( Private::RendererAlgo::renderID( m_renderer.get() ) );
365372

366373
m_controller.reset(
367374
new RenderController( adaptedInPlug(), effectiveContext(), m_renderer )
@@ -413,6 +420,11 @@ Gaffer::ConstContextPtr InteractiveRender::effectiveContext()
413420
}
414421
}
415422

423+
bool InteractiveRender::renderIsActive( const std::string &renderId )
424+
{
425+
return activeRenderIds().count( renderId );
426+
}
427+
416428
void InteractiveRender::stop()
417429
{
418430
// A Catalogue may need to access the render manifest as part of saving out the images from this
@@ -425,6 +437,8 @@ void InteractiveRender::stop()
425437
m_lastRenderManifest = m_controller->renderManifest();
426438
}
427439

440+
activeRenderIds().erase( Private::RendererAlgo::renderID( m_renderer.get() ) );
441+
428442
m_controller.reset();
429443
m_renderer.reset();
430444
m_state = Stopped;

0 commit comments

Comments
 (0)