From 3c1f56724fd2e54ab953891a6fa4acaf9410f980 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 8 Sep 2022 15:06:17 -0700 Subject: [PATCH 01/10] OSLCodeUI : Note UI problem --- python/GafferOSLUI/OSLCodeUI.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/GafferOSLUI/OSLCodeUI.py b/python/GafferOSLUI/OSLCodeUI.py index 3a3bd7ccb8..a4c72e475b 100644 --- a/python/GafferOSLUI/OSLCodeUI.py +++ b/python/GafferOSLUI/OSLCodeUI.py @@ -330,6 +330,9 @@ def __error( self, plug, source, error ) : self.__messageWidget.clear() self.__messageWidget.messageHandler().handle( IECore.Msg.Level.Error, "Compilation error", error ) + + # TODO - for me, this does not actually trigger a redraw - I need to mouse over something that highlights + # in order to trigger a redraw and see the message self.__messageWidget.setVisible( True ) def __shaderCompiled( self ) : From c75e3d86c74ce01123fd12bff0e015d8f8c1201d Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 8 Sep 2022 15:07:41 -0700 Subject: [PATCH 02/10] OSLCode : Wipe shader after compile error --- src/GafferOSL/OSLCode.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/GafferOSL/OSLCode.cpp b/src/GafferOSL/OSLCode.cpp index 1ccb575c93..f1c05510fe 100644 --- a/src/GafferOSL/OSLCode.cpp +++ b/src/GafferOSL/OSLCode.cpp @@ -349,6 +349,13 @@ class CompileProcess : public Gaffer::Process } catch( ... ) { + // As per comment in updateShader(), we probably want to rework this + // so that compilation happens at evaluation time, and we can just throw + // an exception, but in the meantime, I think it's an improvement to + // blank out the shader when it fails, so we at least halt the calculation + // of whatever the OSLCode was previously doing + oslCode->namePlug()->setValue( "" ); + oslCode->typePlug()->setValue( "osl:shader" ); handleException(); } } From 8e64c1be5e35d1238cad586b570f3b5058243ae7 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 8 Sep 2022 15:28:53 -0700 Subject: [PATCH 03/10] GafferImage::Sampler : Add constructor argument to remember context --- include/GafferImage/Sampler.h | 3 ++- include/GafferImage/Sampler.inl | 10 +++++++++- src/GafferImage/Sampler.cpp | 17 +++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/include/GafferImage/Sampler.h b/include/GafferImage/Sampler.h index 8ac92cf17f..f0055dc3c3 100644 --- a/include/GafferImage/Sampler.h +++ b/include/GafferImage/Sampler.h @@ -81,7 +81,7 @@ class GAFFERIMAGE_API Sampler /// @param channelName The channel to sample. /// @param sampleWindow The area from which samples may be requested. It is an error to request samples outside this area. /// @param boundingMode The method of handling samples that fall outside the data window. - Sampler( const GafferImage::ImagePlug *plug, const std::string &channelName, const Imath::Box2i &sampleWindow, BoundingMode boundingMode = Black ); + Sampler( const GafferImage::ImagePlug *plug, const std::string &channelName, const Imath::Box2i &sampleWindow, BoundingMode boundingMode = Black, bool rememberContext = false ); /// Uses `parallelProcessTiles()` to fill the internal tile cache /// with all tiles in the sample window. Allows `sample()` and @@ -135,6 +135,7 @@ class GAFFERIMAGE_API Sampler const std::string m_channelName; Imath::Box2i m_sampleWindow; Imath::Box2i m_dataWindow; + Gaffer::ContextPtr m_overrideContext; std::vector< IECore::ConstFloatVectorDataPtr > m_dataCache; std::vector< const float * > m_dataCacheRaw; diff --git a/include/GafferImage/Sampler.inl b/include/GafferImage/Sampler.inl index f564163b30..a15efec933 100644 --- a/include/GafferImage/Sampler.inl +++ b/include/GafferImage/Sampler.inl @@ -316,7 +316,15 @@ inline void Sampler::cachedData( Imath::V2i p, const float *& tileData, int &til Imath::V2i tileOrigin( p.x & ~( ImagePlug::tileSize() - 1 ), p.y & ~( ImagePlug::tileSize() - 1 ) ); IECore::ConstFloatVectorDataPtr &cacheTilePtr = m_dataCache[ cacheIndex ]; - cacheTilePtr = m_plug->channelData( m_channelName, tileOrigin ); + if( m_overrideContext ) + { + Gaffer::Context::Scope s( m_overrideContext.get() ); + cacheTilePtr = m_plug->channelData( m_channelName, tileOrigin ); + } + else + { + cacheTilePtr = m_plug->channelData( m_channelName, tileOrigin ); + } cacheTileRawPtr = &cacheTilePtr->readable()[0]; } diff --git a/src/GafferImage/Sampler.cpp b/src/GafferImage/Sampler.cpp index 47fc7be11c..b1a613b4d1 100644 --- a/src/GafferImage/Sampler.cpp +++ b/src/GafferImage/Sampler.cpp @@ -43,13 +43,15 @@ using namespace Imath; using namespace Gaffer; using namespace GafferImage; -Sampler::Sampler( const GafferImage::ImagePlug *plug, const std::string &channelName, const Imath::Box2i &sampleWindow, BoundingMode boundingMode ) +Sampler::Sampler( const GafferImage::ImagePlug *plug, const std::string &channelName, const Imath::Box2i &sampleWindow, BoundingMode boundingMode, bool rememberContext ) : m_plug( plug ), m_channelName( channelName ), m_boundingMode( boundingMode ) { + const Context *currentContext = Context::current(); + { - ImagePlug::GlobalScope c( Context::current() ); + ImagePlug::GlobalScope c( currentContext ); if( m_plug->deepPlug()->getValue() ) { @@ -59,6 +61,11 @@ Sampler::Sampler( const GafferImage::ImagePlug *plug, const std::string &channel m_dataWindow = m_plug->dataWindowPlug()->getValue(); } + if( rememberContext ) + { + m_overrideContext = new Context( *currentContext ); + } + // We only store the sample window to be able to perform // validation of the calls made to sample() in debug builds. // The interpolated sample() call generates additional lookups @@ -131,6 +138,12 @@ void Sampler::populate() void Sampler::hash( IECore::MurmurHash &h ) const { + std::unique_ptr< Context::Scope > contextScope; + if( m_overrideContext ) + { + contextScope = std::make_unique( m_overrideContext.get() ); + } + for ( int x = m_cacheWindow.min.x; x < m_cacheWindow.max.x; x += GafferImage::ImagePlug::tileSize() ) { for ( int y = m_cacheWindow.min.y; y < m_cacheWindow.max.y; y += GafferImage::ImagePlug::tileSize() ) From c6134d9b06126eef5e1bdf1b50404eb4239a51e0 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 8 Sep 2022 15:30:43 -0700 Subject: [PATCH 04/10] OSLCode : Hack around hash not including headers --- src/GafferOSL/OSLCode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/GafferOSL/OSLCode.cpp b/src/GafferOSL/OSLCode.cpp index f1c05510fe..906a17aad7 100644 --- a/src/GafferOSL/OSLCode.cpp +++ b/src/GafferOSL/OSLCode.cpp @@ -165,6 +165,7 @@ string generate( const OSLCode *shader, string &shaderName ) { IECore::MurmurHash hash; hash.append( result ); + hash.append( 14 ); // Bump whenever headers change to avoid using stale oso's shaderName = "oslCode" + hash.toString(); } From ae869cbc987916ffbfe59e7c35425eeaa313577b Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 8 Sep 2022 15:52:11 -0700 Subject: [PATCH 05/10] GafferOSL::ShadingEngine : Prototype for sampling arbitrary pixels --- include/GafferOSL/ShadingEngine.h | 15 +- src/GafferOSL/ShadingEngine.cpp | 357 +++++++++++++++++++++++++++++- 2 files changed, 367 insertions(+), 5 deletions(-) diff --git a/include/GafferOSL/ShadingEngine.h b/include/GafferOSL/ShadingEngine.h index e690ac45ac..b76fd2bcdb 100644 --- a/include/GafferOSL/ShadingEngine.h +++ b/include/GafferOSL/ShadingEngine.h @@ -37,11 +37,16 @@ #include "GafferOSL/Export.h" #include "GafferOSL/TypeIds.h" +#include "GafferImage/ImagePlug.h" + #include "IECoreScene/ShaderNetwork.h" #include "IECore/CompoundData.h" +#include "OpenImageIO/ustring.h" + #include "boost/container/flat_set.hpp" +#include "boost/container/flat_map.hpp" namespace GafferOSL { @@ -83,13 +88,17 @@ class GAFFEROSL_API ShadingEngine : public IECore::RefCounted }; using Transforms = std::map; + using ImagePlugs = std::map; /// Append a unique hash representing this shading engine to `h`. void hash( IECore::MurmurHash &h ) const; - IECore::CompoundDataPtr shade( const IECore::CompoundData *points, const Transforms &transforms = Transforms() ) const; + IECore::CompoundDataPtr shade( const IECore::CompoundData *points, const Transforms &transforms = Transforms(), const ImagePlugs &imagePlugs = ImagePlugs() ) const; bool needsAttribute( const std::string &name ) const; bool hasDeformation() const; + bool needsImageSamples() const; + + IECore::MurmurHash hashPossibleImageSamples( const ImagePlugs &imagePlugs ) const; private : @@ -108,6 +117,10 @@ class GAFFEROSL_API ShadingEngine : public IECore::RefCounted bool m_hasDeformation; + std::vector< OIIO::ustring > m_gafferTexturesRequested; + + boost::container::flat_map< OIIO::ustring, int> m_gafferTextureIndices; + void *m_shaderGroupRef; }; diff --git a/src/GafferOSL/ShadingEngine.cpp b/src/GafferOSL/ShadingEngine.cpp index 6105689bae..556f0c7313 100644 --- a/src/GafferOSL/ShadingEngine.cpp +++ b/src/GafferOSL/ShadingEngine.cpp @@ -38,6 +38,9 @@ #include "GafferOSL/OSLShader.h" +#include "GafferImage/FilterAlgo.h" +#include "GafferImage/Sampler.h" + #include "Gaffer/Context.h" #include "IECoreScene/ShaderNetworkAlgo.h" @@ -277,6 +280,24 @@ bool convertValue( void *dst, TypeDesc dstType, const void *src, TypeDesc srcTyp return false; } +// Addresses within this buffer are used for texture handle pointer that correspond to Gaffer textures. The +// data within the buffer isn't used - just the index within the buffer indicates which Gaffer texture is used. +const std::vector< char > g_gafferTextureIndicesBuffer( 1000 ); +tbb::spin_mutex g_gafferTextureIndicesMutex; +const container::flat_map *g_gafferTextureIndices; + +// TODO - this mapping is probably kinda hacky - should we pass the actual filter as part of the texture name instead? +std::vector setupFiltersForInterpModes() +{ + std::vector result( TextureOpt::InterpSmartBicubic ); // Currently SmartBicubic is max value + result[ TextureOpt::InterpBilinear ] = GafferImage::FilterAlgo::acquireFilter( "box" ); + result[ TextureOpt::InterpBicubic ] = GafferImage::FilterAlgo::acquireFilter( "disk" ); + result[ TextureOpt::InterpSmartBicubic ] = GafferImage::FilterAlgo::acquireFilter( "sharp-gaussian" ); + // Note that for TextureOpt::InterpClosest we go straight to Sampler instead of using a filter at all + return result; +} +const std::vector g_filtersForInterpModes = setupFiltersForInterpModes(); + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -286,6 +307,7 @@ bool convertValue( void *dst, TypeDesc dstType, const void *src, TypeDesc srcTyp namespace { +const std::string g_gafferImagePrefix = "gaffer:"; OIIO::ustring gIndex( "shading:index" ); ustring g_contextVariableAttributeScope( "gaffer:context" ); @@ -306,6 +328,56 @@ void maskedDataInitWithZeroDerivs( MaskedData &wval ) } #endif +struct ChannelsRequested +{ + ChannelsRequested( const std::string &gafferTextureName, const ShadingEngine::ImagePlugs &imagePlugs ) + { + imagePlug = nullptr; + + vector nameTokens; + boost::split( nameTokens, gafferTextureName.substr( g_gafferImagePrefix.size() ), boost::is_any_of(".") ); + const auto imagePlugIter = imagePlugs.find( nameTokens[0] ); + if( imagePlugIter != imagePlugs.end() ) + { + imagePlug = imagePlugIter->second; + ConstStringVectorDataPtr channelNamesData = imagePlug->channelNames( &GafferImage::ImagePlug::defaultViewName ); // TODO view + const std::vector< std::string > &channelNames = channelNamesData->readable(); + + std::string channelOrLayer = boost::join( std::vector< string >( nameTokens.begin() + 1, nameTokens.end() ), "." ); + bool found = false; + if( std::find( channelNames.begin(), channelNames.end(), channelOrLayer ) != channelNames.end() ) + { + channels[0] = channels[1] = channels[2] = channels[3] = channelOrLayer; + found = true; + } + else + { + for( int i = 0; i < 4; i++ ) + { + std::string channel = ( channelOrLayer.size() ? channelOrLayer + "." : "" ) + std::string( 1, "RGBA"[i] ); + if( std::find( channelNames.begin(), channelNames.end(), channel ) != channelNames.end() ) + { + channels[i] = channel; + found = true; + } + } + } + + if( !found ) + { + throw IECore::Exception( "Cannot access Gaffer image, cannot find channels matching: " + channelOrLayer + ", available channels are: " + boost::algorithm::join( channelNames, "," ) ); + } + } + else + { + throw IECore::Exception( "Cannot access Gaffer image, cannot find image plug: " + nameTokens[0] ); + } + } + + const GafferImage::ImagePlug* imagePlug; + std::string channels[4]; +}; + class RenderState { @@ -315,7 +387,9 @@ class RenderState const IECore::CompoundData *shadingPoints, const ShadingEngine::Transforms &transforms, const std::vector &contextVariablesNeeded, - const Gaffer::Context *context + const Gaffer::Context *context, + const std::vector< OIIO::ustring > &gafferTexturesRequested, + const ShadingEngine::ImagePlugs &imagePlugs ) { for( @@ -356,6 +430,32 @@ class RenderState ) ); } + + m_gafferTextures.reserve( gafferTexturesRequested.size() ); + Box2i infiniteBound; + infiniteBound.makeInfinite(); + infiniteBound.min += V2i( 1 ); // Make sure overscan doesn't trigger wraparound + infiniteBound.max -= V2i( 1 ); + + for( const auto &textureName : gafferTexturesRequested ) + { + size_t textureIndex = m_gafferTextures.size(); + m_gafferTextures.resize( textureIndex + 1 ); + + // TODO - should this throw, or should we handle it not matching anything? + ChannelsRequested cr( textureName.string(), imagePlugs ); + + Box2i display = cr.imagePlug->format( &GafferImage::ImagePlug::defaultViewName ).getDisplayWindow(); + m_gafferTextures[ textureIndex ].dataWindowSize = display.size(); + for( int i = 0; i < 4; i++ ) + { + if( cr.channels[i] != "" ) + { + // Initialize Sampler + m_gafferTextures[ textureIndex ].channels[i].emplace( cr.imagePlug, cr.channels[i], infiniteBound, GafferImage::Sampler::BoundingMode::Clamp, true ); // TODO select view TODO choose clamp + } + } + } } bool contextVariable( ustring name, TypeDesc type, void *value ) const @@ -502,6 +602,83 @@ class RenderState return false; } + bool texture( size_t gafferTextureIndex, + TextureOpt& options, float s, + float t, float dsdx, float dtdx, float dsdy, + float dtdy, int nchannels, float* result, + float* dresultds, float* dresultdt, + ustring* errormessage ) const + { + if( gafferTextureIndex >= m_gafferTextures.size() ) + { + throw IECore::Exception( "Internal Gaffer error, out of bound texture index" ); + } + + const GafferTextureData &tex = m_gafferTextures[ gafferTextureIndex ]; + // TODO - why is nchannels always 4? It should be 1 or 3 + // TODO - alpha + memset( result, 0, sizeof( float ) * nchannels ); + try + { + V2f p = V2f( s, t ) * tex.dataWindowSize; + for( int i = 0; i < nchannels; i++ ) + { + if( tex.channels[i] ) + { + // TODO - optimize case where someone accidentally accesses channel as layer? + /*if( i > 0 && tex.channels[i] == tex.channels[i - 1] ) + { + result[i] = result[i-1]; + }*/ + if( options.interpmode == TextureOpt::InterpClosest ) + { + // TODO - default floor is incredibly slow - these two calls are actually quite prominent in profiles + result[i] = tex.channels[i]->sample( int( floor( p[0] ) ), int( floor( p[1] ) ) ); + } + else if( dsdx == 0 && dtdx == 0 && dsdy == 0 && dtdy == 0 ) + { + result[i] = tex.channels[i]->sample( p[0], p[1] ); + } + else if( ( dsdx == 0 && dtdy == 0 ) || ( dtdx == 0 && dsdy == 0 ) ) + { + result[i] = GafferImage::FilterAlgo::sampleBox( *tex.channels[i], p, + ( dsdx ? dsdx : dtdx ) * tex.dataWindowSize.x, ( dsdy ? dsdy : dtdy ) * tex.dataWindowSize.y, + g_filtersForInterpModes[ options.interpmode ], m_scratchMemory + ); + } + else + { + // TODO - confirm scaling is correct on non-square image + result[i] = GafferImage::FilterAlgo::sampleParallelogram( *tex.channels[i], p, + V2f( dsdx, dtdx ) * tex.dataWindowSize.x, V2f( dsdy, dtdy ) * tex.dataWindowSize.y, + g_filtersForInterpModes[ options.interpmode ] + ); + } + } + } + } + catch( IECore::Cancelled const &c ) + { + // TODO - figure out why letting this exception through causes std::terminate + } + catch( std::exception const &e ) + { + msg( Msg::Warning, "ShadingEngine", "Error during Gaffer texture() eval: " + std::string( e.what() ) ); + } + + if( dresultds ) + { + memset( dresultds, 0, sizeof( float ) * nchannels ); + } + + if( dresultdt ) + { + memset( dresultds, 0, sizeof( float ) * nchannels ); + } + + return true; + } + private : using RenderStateTransforms = boost::unordered_map< OIIO::ustring, ShadingEngine::Transform, OIIO::ustringHash>; @@ -519,9 +696,23 @@ class RenderState ConstDataPtr dataStorage; }; + struct GafferTextureData + { + // TODO - mutable doesn't make sense here, but it kind of would make sense if the stuff that + // updates during a call to sample() was mutable, so sample() on a Sampler could be const + // + // TODO - we probably want to share samplers between threads, but currently, Sampler isn't threadsafe? + mutable std::optional channels[4]; + V2f dataWindowSize; + }; + container::flat_map m_userData; container::flat_map m_contextVariables; + std::vector m_gafferTextures; + + // Each thread gets its own copy of RenderState, so we can safely put scratch memory here + mutable std::vector< float > m_scratchMemory; }; struct ThreadRenderState @@ -531,6 +722,25 @@ struct ThreadRenderState const RenderState& renderState; }; +int g_gafferTextureHandleMagicNumber = -42; +class GafferTextureHandle +{ +public: + GafferTextureHandle() : m_oiioTexSysRefCount( g_gafferTextureHandleMagicNumber ) + { + } + + bool isGafferTextureHandle() + { + return m_oiioTexSysRefCount == g_gafferTextureHandleMagicNumber; + } + +private: + + + const OIIO::atomic_int m_oiioTexSysRefCount; +}; + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -798,6 +1008,80 @@ class RendererServices : public OSL::RendererServices GafferBatchedRendererServices<8> m_batchedRendererServices8; GafferBatchedRendererServices<16> m_batchedRendererServices16; #endif + +public: + virtual bool has_userdata( ustring name, TypeDesc type, OSL::ShaderGlobals *sg ) + { + const ThreadRenderState *threadRenderState = sg ? static_cast( sg->renderstate ) : nullptr; + if( !threadRenderState ) + { + return false; + } + return threadRenderState->renderState.userData( threadRenderState->pointIndex, name, type, nullptr ); + } + + TextureHandle * get_texture_handle( ustring filename, ShadingContext *context ) override + { + if( boost::starts_with( filename, g_gafferImagePrefix ) ) + { + if( !g_gafferTextureIndices ) + { + throw IECore::Exception( "Should not be possible\n" ); + } + auto i = g_gafferTextureIndices->find( filename ); + if( i == g_gafferTextureIndices->end() ) + { + throw IECore::Exception( "Cannot access Gaffer image, it was not visible during compilation: " + filename.string() ); + } + else + { + if( i->second > (int)g_gafferTextureIndicesBuffer.size() ) + { + throw IECore::Exception( "GafferOSL::ShadingEngine Too many unique Gaffer images used." ); + } + return (TextureHandle*)( &g_gafferTextureIndicesBuffer[ i->second ] ); + } + } + + return OSL::RendererServices::get_texture_handle( filename, context ); + + + //return texturesys()->get_texture_handle( filename, context->texture_thread_info() ); + } + + bool texture( ustring filename, TextureHandle* texture_handle, + TexturePerthread* texture_thread_info, + TextureOpt& options, ShaderGlobals* sg, float s, + float t, float dsdx, float dtdx, float dsdy, + float dtdy, int nchannels, float* result, + float* dresultds, float* dresultdt, + ustring* errormessage) override + { + if( texture_handle ) + { + size_t gafferTextureIndex = ((char*)texture_handle) - &g_gafferTextureIndicesBuffer[0]; + + if( gafferTextureIndex <= g_gafferTextureIndicesBuffer.size() ) + { + const ThreadRenderState *threadRenderState = sg ? static_cast( sg->renderstate ) : nullptr; + if( threadRenderState->renderState.texture( + gafferTextureIndex, options, + s, t, dsdx, dtdx, dsdy, dtdy, + nchannels, result, dresultds, dresultdt, errormessage + ) ) + { + return true; + } + } + } + else if( boost::starts_with( filename, g_gafferImagePrefix ) ) + { + throw IECore::Exception( "Cannot access Gaffer image, it was not visible during compilation: " + filename.string() ); + } + + return OSL::RendererServices::texture( filename, texture_handle, texture_thread_info, options, sg, s, t, dsdx, dtdx, dsdy, dtdy, nchannels, result, dresultds, dresultdt, errormessage ); + } + }; } // namespace @@ -1412,6 +1696,23 @@ void ShadingEngine::queryShaderGroup() } } } + + int numTextures = 0; + shadingSystem->getattribute( &shaderGroup, "num_textures_needed", numTextures ); + if( numTextures ) + { + ustring *textureNames = nullptr; + shadingSystem->getattribute( &shaderGroup, "textures_needed", TypeDesc::PTR, &textureNames ); + for( int i = 0; i < numTextures; ++i ) + { + if( boost::starts_with( textureNames[i], g_gafferImagePrefix ) ) + { + m_gafferTextureIndices[ textureNames[i] ] = m_gafferTexturesRequested.size(); + m_gafferTexturesRequested.push_back( textureNames[i] ); + + } + } + } } ShadingEngine::~ShadingEngine() @@ -1455,11 +1756,21 @@ struct ExecuteShadeParameters mutable tbb::enumerable_thread_specific threadInfoCache; }; + IECore::CompoundDataPtr executeShade( const ExecuteShadeParameters ¶ms, const RenderState &renderState, ShaderGroup &shaderGroup, ShadingSystem *shadingSystem ) { // Allocate data for the result ShadingResults results( params.numPoints ); + { + // Do a quick init of the shading system while setting the global map of texture indices + // to map the correct textures for this ShadingEngine + tbb::spin_mutex::scoped_lock lock( g_gafferTextureIndicesMutex ); + g_gafferTextureIndices = &m_gafferTextureIndices; + shadingSystem->execute_init( *params.threadInfoCache.local().shadingContext, shaderGroup, shaderGlobals, false ); + g_gafferTextureIndices = nullptr; + } + // Iterate over the input points, doing the shading as we go auto f = [¶ms, &renderState, &shaderGroup, &shadingSystem, &results]( const tbb::blocked_range &r ) { @@ -1512,7 +1823,12 @@ IECore::CompoundDataPtr executeShade( const ExecuteShadeParameters ¶ms, cons // tasks from propagating down and stopping our tasks from being started. // Otherwise we silently return results with black gaps where tasks were omitted. tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); - tbb::parallel_for( tbb::blocked_range( 0, params.numPoints, 5000 ), f, taskGroupContext ); + + // TODO TODO TODO - since we haven't yet figured out how to multithread Sampler, currently we just crash + // if multiple threads pick up here while we're sampling images. We can hack around this temporarily + // since we're only sampling images when processing image tiles, so we can just set the block size larger than + // an image tile, but this is not at all sustainable. + tbb::parallel_for( tbb::blocked_range( 0, params.numPoints, 50000 ), f, taskGroupContext ); return results.results(); } @@ -1634,7 +1950,7 @@ IECore::CompoundDataPtr executeShadeBatched( const ExecuteShadeParameters ¶m } // namespace -IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points, const ShadingEngine::Transforms &transforms ) const +IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points, const ShadingEngine::Transforms &transforms, const ImagePlugs &imagePlugs ) const { ShaderGroup &shaderGroup = **static_cast( m_shaderGroupRef ); @@ -1701,7 +2017,7 @@ IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points // Add a RenderState to the ShaderGlobals. This will // get passed to our RendererServices queries. - RenderState renderState( points, transforms, m_contextVariablesNeeded, context ); + RenderState renderState( points, transforms, m_contextVariablesNeeded, context, m_gafferTexturesRequested, imagePlugs ); #if OSL_USE_BATCHED if( batchSize == 1 ) @@ -1752,3 +2068,36 @@ bool ShadingEngine::hasDeformation() const { return m_hasDeformation; } + +bool ShadingEngine::needsImageSamples() const +{ + return m_gafferTexturesRequested.size() > 0; +} + +IECore::MurmurHash ShadingEngine::hashPossibleImageSamples( const ImagePlugs &imagePlugs ) const +{ + IECore::MurmurHash h; + + + for( const auto &textureName : m_gafferTexturesRequested ) + { + ChannelsRequested cr( textureName.string(), imagePlugs ); + Box2i dw = cr.imagePlug->dataWindow( &GafferImage::ImagePlug::defaultViewName ); + h.append( dw ); + for( int i = 0; i < 4; i++ ) + { + if( cr.channels[i] != "" ) + { + // TODO - parallelize + for ( int x = dw.min.x; x < dw.max.x; x += GafferImage::ImagePlug::tileSize() ) + { + for ( int y = dw.min.y; y < dw.max.y; y += GafferImage::ImagePlug::tileSize() ) + { + h.append( cr.imagePlug->channelDataHash( cr.channels[i], Imath::V2i( x, y ) ) ); + } + } + } + } + } + return h; +} From e1895f523de3de7abaa85b92282f68d74e968886 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Thu, 8 Sep 2022 15:34:41 -0700 Subject: [PATCH 06/10] OSLImage::ShadingEngine : Use image sampling support --- include/GafferOSL/OSLImage.h | 6 +++ src/GafferOSL/OSLImage.cpp | 79 +++++++++++++++++++++++++++++---- src/GafferOSL/ShadingEngine.cpp | 35 ++++++++------- 3 files changed, 96 insertions(+), 24 deletions(-) diff --git a/include/GafferOSL/OSLImage.h b/include/GafferOSL/OSLImage.h index 30c1fad0e3..685459c44a 100644 --- a/include/GafferOSL/OSLImage.h +++ b/include/GafferOSL/OSLImage.h @@ -84,6 +84,9 @@ class GAFFEROSL_API OSLImage : public GafferImage::ImageProcessor GafferImage::Format computeFormat( const Gaffer::Context *context, const GafferImage::ImagePlug *parent ) const override; Imath::Box2i computeDataWindow( const Gaffer::Context *context, const GafferImage::ImagePlug *parent ) const override; + Gaffer::ValuePlug::CachePolicy hashCachePolicy( const Gaffer::ValuePlug *output ) const override; + + private : GafferScene::ShaderPlug *shaderPlug(); @@ -105,6 +108,9 @@ class GAFFEROSL_API OSLImage : public GafferImage::ImageProcessor Gaffer::StringVectorDataPlug *affectedChannelsPlug(); const Gaffer::StringVectorDataPlug *affectedChannelsPlug() const; + Gaffer::IntPlug *allImageDataNeededPlug(); + const Gaffer::IntPlug *allImageDataNeededPlug() const; + void hashShading( const Gaffer::Context *context, IECore::MurmurHash &h ) const; IECore::ConstCompoundDataPtr computeShading( const Gaffer::Context *context ) const; diff --git a/src/GafferOSL/OSLImage.cpp b/src/GafferOSL/OSLImage.cpp index ce3c743342..17ec6b76c3 100644 --- a/src/GafferOSL/OSLImage.cpp +++ b/src/GafferOSL/OSLImage.cpp @@ -76,6 +76,7 @@ OSLImage::OSLImage( const std::string &name ) addChild( new Gaffer::ObjectPlug( "__shading", Gaffer::Plug::Out, new CompoundData() ) ); addChild( new Gaffer::StringVectorDataPlug( "__affectedChannels", Gaffer::Plug::Out, new StringVectorData() ) ); + addChild( new Gaffer::IntPlug( "__allImageDataNeeded", Gaffer::Plug::Out ) ); addChild( new Plug( "channels", Plug::In, Plug::Default & ~Plug::AcceptsInputs ) ); addChild( new OSLCode( "__oslCode" ) ); @@ -139,44 +140,54 @@ const Gaffer::StringVectorDataPlug *OSLImage::affectedChannelsPlug() const return getChild( g_firstPlugIndex + 3 ); } +Gaffer::IntPlug *OSLImage::allImageDataNeededPlug() +{ + return getChild( g_firstPlugIndex + 4 ); +} + +const Gaffer::IntPlug *OSLImage::allImageDataNeededPlug() const +{ + return getChild( g_firstPlugIndex + 4 ); +} + Gaffer::Plug *OSLImage::channelsPlug() { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } const Gaffer::Plug *OSLImage::channelsPlug() const { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } GafferOSL::OSLCode *OSLImage::oslCode() { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 6 ); } const GafferOSL::OSLCode *OSLImage::oslCode() const { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 6 ); } GafferImage::Constant *OSLImage::defaultConstant() { - return getChild( g_firstPlugIndex + 6 ); + return getChild( g_firstPlugIndex + 7 ); } const GafferImage::Constant *OSLImage::defaultConstant() const { - return getChild( g_firstPlugIndex + 6 ); + return getChild( g_firstPlugIndex + 7 ); } GafferImage::ImagePlug *OSLImage::defaultInPlug() { - return getChild( g_firstPlugIndex + 7 ); + return getChild( g_firstPlugIndex + 8 ); } const GafferImage::ImagePlug *OSLImage::defaultInPlug() const { - return getChild( g_firstPlugIndex + 7 ); + return getChild( g_firstPlugIndex + 8 ); } const GafferImage::ImagePlug *OSLImage::defaultedInPlug() const @@ -205,6 +216,12 @@ void OSLImage::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outpu input == inPlug()->deepPlug() || input == inPlug()->sampleOffsetsPlug() ) + { + outputs.push_back( shadingPlug() ); + outputs.push_back( allImageDataNeededPlug() ); + } + + if( input == allImageDataNeededPlug() ) { outputs.push_back( shadingPlug() ); } @@ -298,6 +315,25 @@ void OSLImage::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *con } } } + else if( output == allImageDataNeededPlug() ) + { + ConstShadingEnginePtr shadingEngine; + if( auto shader = runTimeCast( shaderPlug()->source()->node() ) ) + { + ImagePlug::GlobalScope globalScope( context ); + shadingEngine = shader->shadingEngine(); + } + + if( !shadingEngine ) + { + throw IECore::Exception( "__allImageDataNeeded may only be hashed when there is a shadingEngine" ); + } + + ShadingEngine::ImagePlugs shadingInputImages; + shadingInputImages["in"] = inPlug(); + + h.append( shadingEngine->hashPossibleImageSamples( shadingInputImages ) ); + } } void OSLImage::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const @@ -365,6 +401,10 @@ void OSLImage::compute( Gaffer::ValuePlug *output, const Gaffer::Context *contex StringVectorDataPtr resultVector = new StringVectorData( vector( result.begin(), result.end() ) ); static_cast( output )->setValue( resultVector ); } + else if( output == allImageDataNeededPlug() ) + { + throw IECore::Exception( "__allImageDataNeeded plug should only ever be hashed, never computed" ); + } ImageProcessor::compute( output, context ); } @@ -488,6 +528,15 @@ Imath::Box2i OSLImage::computeDataWindow( const Gaffer::Context *context, const return defaultedInPlug()->dataWindowPlug()->getValue(); } +Gaffer::ValuePlug::CachePolicy OSLImage::hashCachePolicy( const Gaffer::ValuePlug *output ) const +{ + if( output == allImageDataNeededPlug() ) + { + return ValuePlug::CachePolicy::TaskCollaboration; + } + return ImageProcessor::hashCachePolicy( output ); +} + void OSLImage::hashShading( const Gaffer::Context *context, IECore::MurmurHash &h ) const { ConstShadingEnginePtr shadingEngine; @@ -512,6 +561,11 @@ void OSLImage::hashShading( const Gaffer::Context *context, IECore::MurmurHash & defaultedInPlug()->formatPlug()->hash( h ); channelNamesData = defaultedInPlug()->channelNamesPlug()->getValue(); deep = defaultedInPlug()->deepPlug()->getValue(); + + if( shadingEngine->needsImageSamples() ) + { + allImageDataNeededPlug()->hash( h ); + } } if( deep ) @@ -642,9 +696,16 @@ IECore::ConstCompoundDataPtr OSLImage::computeShading( const Gaffer::Context *co shadingPoints->writable()["P"] = pData; shadingPoints->writable()["u"] = uData; shadingPoints->writable()["v"] = vData; + shadingPoints->writable()["dudx"] = new FloatData( uvStep[0] ); + shadingPoints->writable()["dudy"] = new FloatData( 0 ); + shadingPoints->writable()["dvdx"] = new FloatData( 0 ); + shadingPoints->writable()["dvdy"] = new FloatData( uvStep[1] ); + ShadingEngine::Transforms transforms; + ShadingEngine::ImagePlugs shadingInputImages; + shadingInputImages["in"] = inPlug(); - CompoundDataPtr result = shadingEngine->shade( shadingPoints.get() ); + CompoundDataPtr result = shadingEngine->shade( shadingPoints.get(), transforms, shadingInputImages ); // remove results that aren't suitable to become channels for( CompoundDataMap::iterator it = result->writable().begin(); it != result->writable().end(); ) diff --git a/src/GafferOSL/ShadingEngine.cpp b/src/GafferOSL/ShadingEngine.cpp index 556f0c7313..85218e2863 100644 --- a/src/GafferOSL/ShadingEngine.cpp +++ b/src/GafferOSL/ShadingEngine.cpp @@ -602,12 +602,14 @@ class RenderState return false; } - bool texture( size_t gafferTextureIndex, - TextureOpt& options, float s, - float t, float dsdx, float dtdx, float dsdy, - float dtdy, int nchannels, float* result, - float* dresultds, float* dresultdt, - ustring* errormessage ) const + bool texture( + size_t gafferTextureIndex, + TextureOpt& options, float s, + float t, float dsdx, float dtdx, float dsdy, + float dtdy, int nchannels, float* result, + float* dresultds, float* dresultdt, + ustring* errormessage + ) const { if( gafferTextureIndex >= m_gafferTextures.size() ) { @@ -642,9 +644,10 @@ class RenderState else if( ( dsdx == 0 && dtdy == 0 ) || ( dtdx == 0 && dsdy == 0 ) ) { result[i] = GafferImage::FilterAlgo::sampleBox( *tex.channels[i], p, - ( dsdx ? dsdx : dtdx ) * tex.dataWindowSize.x, ( dsdy ? dsdy : dtdy ) * tex.dataWindowSize.y, + ( dsdx ? dsdx : dtdx ) * tex.dataWindowSize.x, + ( dsdy ? dsdy : dtdy ) * tex.dataWindowSize.y, g_filtersForInterpModes[ options.interpmode ], m_scratchMemory - ); + ); } else { @@ -1049,13 +1052,15 @@ class RendererServices : public OSL::RendererServices //return texturesys()->get_texture_handle( filename, context->texture_thread_info() ); } - bool texture( ustring filename, TextureHandle* texture_handle, - TexturePerthread* texture_thread_info, - TextureOpt& options, ShaderGlobals* sg, float s, - float t, float dsdx, float dtdx, float dsdy, - float dtdy, int nchannels, float* result, - float* dresultds, float* dresultdt, - ustring* errormessage) override + bool texture( + ustring filename, TextureHandle* texture_handle, + TexturePerthread* texture_thread_info, + TextureOpt& options, ShaderGlobals* sg, float s, + float t, float dsdx, float dtdx, float dsdy, + float dtdy, int nchannels, float* result, + float* dresultds, float* dresultdt, + ustring* errormessage + ) override { if( texture_handle ) { From 42df29bc278c600b2b5c25c824d06f2103e9c08a Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Tue, 30 Aug 2022 16:04:09 -0700 Subject: [PATCH 07/10] GafferOSL/ImageProcessing : Add wrappers for pixel access --- shaders/GafferOSL/ImageProcessing.h | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/shaders/GafferOSL/ImageProcessing.h b/shaders/GafferOSL/ImageProcessing.h index ecfc13974e..bdfeedeaaf 100644 --- a/shaders/GafferOSL/ImageProcessing.h +++ b/shaders/GafferOSL/ImageProcessing.h @@ -81,4 +81,73 @@ closure color outLayer( string layerName, color layerColor ) return outChannel( redName, layerColor[0] ) + outChannel( greenName, layerColor[1] ) + outChannel( blueName, layerColor[2] ); } + +string gafferFilterToOiioFilter( string s ) +{ + if( s == "gaussian" ) + { + return "smartcubic"; + } + else if( s == "disk" ) + { + return "cubic"; + } + else + { + return "linear"; + } +} + +// TODO - figure out defaultValue +// TODO - figure out alpha +float pixel( string channelName, point p ) +{ + return texture( concat( "gaffer:in.", channelName ), p[0] * Dx(u), p[1] * Dy(v), 0, 0, 0, 0, "interp", "closest" ); +} + +float pixelBilinear( string channelName, point p ) +{ + return texture( concat( "gaffer:in.", channelName ), p[0] * Dx(u), p[1] * Dy(v), 0, 0, 0, 0, "interp", "bilinear" ); +} + +float pixelFiltered( string channelName, point p, float dx, float dy, string filter ) +{ + return texture( concat( "gaffer:in.", channelName ), p[0] * Dx(u), p[1] * Dy(v), + dx * Dx(u), 0, 0, dy * Dy(v), "interp", gafferFilterToOiioFilter( filter ) + ); +} + +float pixelFilteredWithDirections( string channelName, point p, vector dpdx, vector dpdy, string filter ) +{ + return texture( concat( "gaffer:in.", channelName ), p[0] * Dx(u), p[1] * Dy(v), + dpdx[0] * Dx(u), dpdx[1] * Dx(u), dpdy[0] * Dy(v), dpdy[1] * Dy(v), + "interp", gafferFilterToOiioFilter( filter ) + ); +} + +color pixel( string layerName, point p ) +{ + return texture( concat( "gaffer:in.", layerName ), p[0] * Dx(u), p[1] * Dy(v), 0, 0, 0, 0, "interp", "closest" ); +} + +color pixelBilinear( string layerName, point p ) +{ + return texture( concat( "gaffer:in.", layerName ), p[0] * Dx(u), p[1] * Dy(v), 0, 0, 0, 0, "interp", "bilinear" ); +} + +color pixelFiltered( string layerName, point p, float dx, float dy, string filter ) +{ + return texture( concat( "gaffer:in.", layerName ), p[0] * Dx(u), p[1] * Dy(v), + dx * Dx(u), 0, 0, dy * Dy(v), "interp", gafferFilterToOiioFilter( filter ) + ); +} + +color pixelFilteredWithDirections( string layerName, point p, vector dpdx, vector dpdy, string filter ) +{ + return texture( concat( "gaffer:in.", layerName ), p[0] * Dx(u), p[1] * Dy(v), + dpdx[0] * Dx(u), dpdx[1] * Dx(u), dpdy[0] * Dy(v), dpdy[1] * Dy(v), + "interp", gafferFilterToOiioFilter( filter ) + ); +} + #endif // GAFFEROSL_IMAGEPROCESSING_H From 7aa3bf264ac48fd79381b2cde14d003a7d31d2d5 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Wed, 7 Sep 2022 18:05:26 -0700 Subject: [PATCH 08/10] OSLImageTest : Test pixel sampling --- python/GafferOSLTest/OSLImageTest.py | 202 ++++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 7 deletions(-) diff --git a/python/GafferOSLTest/OSLImageTest.py b/python/GafferOSLTest/OSLImageTest.py index 08d54ed24e..03407f146c 100644 --- a/python/GafferOSLTest/OSLImageTest.py +++ b/python/GafferOSLTest/OSLImageTest.py @@ -121,7 +121,7 @@ def checkDirtiness( expected): ] checkDirtiness( channelsDirtied + [ - "channels", "__shader", "__shading", + "channels", "__shader", "__allImageDataNeeded", "__shading", "__affectedChannels", "out.channelNames", "out.channelData", "out" ] ) @@ -155,13 +155,13 @@ def checkDirtiness( expected): getGreen["parameters"]["channelName"].setValue( "R" ) checkDirtiness( channelsDirtied + [ - "channels", "__shader", "__shading", + "channels", "__shader", "__allImageDataNeeded", "__shading", "__affectedChannels", "out.channelNames", "out.channelData", "out" ] ) floatToColor["parameters"]["r"].setInput( getRed["out"]["channelValue"] ) checkDirtiness( channelsDirtied + [ - "channels", "__shader", "__shading", + "channels", "__shader", "__allImageDataNeeded", "__shading", "__affectedChannels", "out.channelNames", "out.channelData", "out" ] ) @@ -178,14 +178,14 @@ def checkDirtiness( expected): image["in"].setInput( None ) checkDirtiness( [ 'in.viewNames', 'in.format', 'in.dataWindow', 'in.metadata', 'in.deep', 'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in', - 'out.viewNames', '__shading', '__affectedChannels', + 'out.viewNames', '__allImageDataNeeded', '__shading', '__affectedChannels', 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out.metadata', 'out.deep', 'out.sampleOffsets', 'out' ] ) image["defaultFormat"]["displayWindow"]["max"]["x"].setValue( 200 ) checkDirtiness( [ 'defaultFormat.displayWindow.max.x', 'defaultFormat.displayWindow.max', 'defaultFormat.displayWindow', 'defaultFormat', - '__defaultIn.format', '__defaultIn.dataWindow', '__defaultIn', '__shading', '__affectedChannels', + '__defaultIn.format', '__defaultIn.dataWindow', '__defaultIn', '__allImageDataNeeded', '__shading', '__affectedChannels', 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out' ] ) @@ -194,7 +194,7 @@ def checkDirtiness( expected): checkDirtiness( [ 'in.viewNames', 'in.format', 'in.dataWindow', 'in.metadata', 'in.deep', 'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in', - 'out.viewNames', '__shading', '__affectedChannels', + 'out.viewNames', '__allImageDataNeeded', '__shading', '__affectedChannels', 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out.metadata', 'out.deep', 'out.sampleOffsets', 'out' ] ) @@ -236,7 +236,6 @@ def checkDirtiness( expected): ) ) - def testAcceptsShaderSwitch( self ) : script = Gaffer.ScriptNode() @@ -1007,5 +1006,194 @@ def testEval( channelName ): self.assertEqual( testEval("G"), [ 250, 750, 239, 597, 230, 320, 300, 265, 457, 446, 652, 652, 882, 882, 1000, 1000 ] ) self.assertEqual( testEval("B"), [ 700, 700, 684, 666, 642, 608, 518, 632, 308, 742, 253, 697, 345, 505, 400, 400 ] ) + def testImageSampling( self ): + + reader = GafferImage.ImageReader() + reader["fileName"].setValue( os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgbOverChecker.100x100.exr" ) ) + + switchWindow = GafferImage.Crop() + switchWindow["in"].setInput( reader["out"] ) + switchWindow["area"].setValue( imath.Box2i( imath.V2i( 50, 50 ), imath.V2i( 100, 100 ) ) ) + switchWindow["affectDataWindow"].setValue( False ) + switchWindow["affectDisplayWindow"].setValue( False ) + + deleteChannels = GafferImage.DeleteChannels() + deleteChannels["in"].setInput( switchWindow["out"] ) + deleteChannels["channels"].setValue( 'A' ) + + code = GafferOSL.OSLCode() + code["out"].addChild( Gaffer.Color3fPlug( "out", direction = Gaffer.Plug.Direction.Out ) ) + + # It can be pretty confusing to make syntax errors in this file unless we actually catch and report + # the errors manually. I wonder if we could refactor OSLCode to not swallow exceptions ... I think + # we've moved to more CatchingSiganlCombiner since the comment there was written? + checkErrors = GafferTest.CapturingSlot( code.errorSignal() ) + + code["code"].setValue( """ + out = pixel( "", P + vector( -5, -3, 0 ) ); + out.g = pixel( "G", P + vector( -5, -3, 0 ) ); + """) + self.assertEqual( list( checkErrors ), [] ) + + oslImage = GafferOSL.OSLImage() + oslImage["in"].setInput( deleteChannels["out"] ) + oslImage["channels"].addChild( Gaffer.NameValuePlug( "", Gaffer.Color3fPlug( "value" ), True, "channel", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) + oslImage["channels"]["channel"]["value"].setInput( code["out"]["out"] ) + + crop = GafferImage.Crop() + crop["in"].setInput( oslImage["out"] ) + crop["area"].setValue( imath.Box2i( imath.V2i( 5, 3 ), imath.V2i( 100, 100 ) ) ) + crop["affectDisplayWindow"].setValue( False ) + + offset = GafferImage.Offset() + offset["in"].setInput( deleteChannels["out"] ) + offset["offset"].setValue( imath.V2i( 5, 3 ) ) + + referenceCrop = GafferImage.Crop() + referenceCrop["in"].setInput( offset["out"] ) + referenceCrop["area"].setInput( crop["area"] ) + referenceCrop["affectDisplayWindow"].setValue( False ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True ) + + code["code"].setValue( """ + out = pixel( "", P + vector( -4.6, -3.4, 0 ) ); + out.g = pixel( "G", P + vector( -4.6, -3.4, 0 ) ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True ) + + code["code"].setValue( """ + out = pixel( "", P + vector( -5.4, -2.6, 0 ) ); + out.g = pixel( "G", P + vector( -5.4, -2.6, 0 ) ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True ) + + code["code"].setValue( """ + out = pixel( "", P + vector( -5.6, -2.6, 0 ) ); + out.g = pixel( "G", P + vector( -5.6, -2.6, 0 ) ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + + with self.assertRaises( Exception ): + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True ) + + code["code"].setValue( """ + out = pixel( "", P + vector( -5.3, -2.6, 0 ) ); + out.g = pixel( "G", P + vector( -5.3, -2.6, 0 ) ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + + switchWindow["affectDisplayWindow"].setValue( True ) + crop["area"].setValue( imath.Box2i( imath.V2i( 5, 3 ), imath.V2i( 50, 50 ) ) ) + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True ) + + switchWindow["affectDisplayWindow"].setValue( False ) + switchWindow["affectDataWindow"].setValue( True ) + crop["area"].setValue( imath.Box2i( imath.V2i( 55, 53 ), imath.V2i( 100, 100 ) ) ) + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True ) + + switchWindow["affectDataWindow"].setValue( False ) + + resample = GafferImage.Resample() + resample["in"].setInput( deleteChannels["out"] ) + resample["filter"].setValue( "triangle" ) + resample["matrix"].setValue( imath.M33f().translate( imath.V2f( 5, 3 ) ) ) + + referenceCrop["in"].setInput( resample["out"] ) + + crop["area"].setValue( imath.Box2i( imath.V2i( 5, 3 ), imath.V2i( 100, 100 ) ) ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.000001 ) + + resample["matrix"].setValue( imath.M33f().translate( imath.V2f( 4.7, 9.1 ) ) ) + + crop["area"].setValue( imath.Box2i( imath.V2i( 10, 10 ), imath.V2i( 100, 100 ) ) ) + + code["code"].setValue( """ + out = pixelBilinear( "", P + vector( -4.7, -9.1, 0 ) ); + out.g = pixelBilinear( "G", P + vector( -4.7, -9.1, 0 ) ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.00001 ) + + resample["matrix"].setValue( imath.M33f().scale( 1.5 ) * imath.M33f().translate( imath.V2f( -11.6, -13.3 ) ) ) + + code["code"].setValue( """ + out = pixelBilinear( "", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5 ); + out.g = pixelBilinear( "G", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5 ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + crop["area"].setValue( imath.Box2i( imath.V2i( 0, 0 ), imath.V2i( 100, 100 ) ) ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.00001 ) + + code["code"].setValue( """ + out = pixelFiltered( "", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5, 7, 7, "gaussian" ); + out.g = pixelFiltered( "G", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5, 7, 7, "gaussian" ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + resample["filter"].setValue( "sharp-gaussian" ) + resample["filterScale"].setValue( imath.V2f( 7 ) ) + + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.00001 ) + + resample["filter"].setValue( "disk" ) + code["code"].setValue( """ + out = pixelFiltered( "", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5, 7, 7, "disk" ); + out.g = pixelFiltered( "G", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5, 7, 7, "disk" ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.00001 ) + + resample["filterScale"].setValue( imath.V2f( 7, 2 ) ) + code["code"].setValue( """ + out = pixelFiltered( "", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5, 7, 2, "disk" ); + out.g = pixelFiltered( "G", ( P + vector( 11.6, 13.3, 0 ) ) / 1.5, 7, 2, "disk" ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.00001 ) + + + # Test a weird angled filter by rotating to an angle, applying an anisotropic filter, and rotating + # back + imageTransform = GafferImage.ImageTransform() + imageTransform["transform"]["rotate"].setValue( 30 ) + imageTransform["in"].setInput( deleteChannels["out"] ) + + resample["in"].setInput( imageTransform["out"] ) + resample["matrix"].setValue( imath.M33f() ) + resample["filter"].setValue( "sharp-gaussian" ) + + imageTransformReverse = GafferImage.ImageTransform() + imageTransformReverse["in"].setInput( resample["out"] ) + imageTransformReverse["transform"]["rotate"].setValue( -30 ) + imageTransformReverse["transform"]["translate"].setValue( imath.V2f( 5.5, 7.5 ) ) + + referenceCrop["in"].setInput( imageTransformReverse["out"] ) + + # Note that the filter is larger than <7, 2>, in order to compensate for the extra smearing from + # the rotates + code["code"].setValue( """ + vector dir = rotate( vector( 1, 0, 0 ), 30 * M_PI / 180, vector( 0, 0, -1 ) ); + vector perp = vector( dir.y, -dir.x, 0 ); + out = pixelFilteredWithDirections( + "", ( P + vector( -5.5, -7.5, 0 ) ), dir * 7.3, perp * 2.9, "gaussian" + ); + out.g = pixelFilteredWithDirections( + "G", ( P + vector( -5.5, -7.5, 0 ) ), dir * 7.3, perp * 2.9, "gaussian" + ); + """ ) + self.assertEqual( list( checkErrors ), [] ) + + crop["area"].setValue( imath.Box2i( imath.V2i( 12, 12 ), imath.V2i( 95, 95 ) ) ) + + # With the imprecision of the rotate/smear/rotate approach, this isn't an exact match, but it's close + self.assertImagesEqual( crop["out"], referenceCrop["out"], ignoreChannelNamesOrder = True, maxDifference = 0.01 ) + if __name__ == "__main__": unittest.main() From a91b1b0bd29b90ac7543a4ec1214e9e065bd8663 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Mon, 27 May 2024 16:38:32 -0700 Subject: [PATCH 09/10] SConstruct : Don't link cycles libraries twice --- SConstruct | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/SConstruct b/SConstruct index 55b44771b7..d3056deefe 100644 --- a/SConstruct +++ b/SConstruct @@ -1040,6 +1040,24 @@ cyclesDefines = [ ( "WITH_OPTIX" ), ] +cyclesLibraries = [ + "cycles_session", "cycles_scene", "cycles_graph", "cycles_bvh", "cycles_device", "cycles_kernel", "cycles_kernel_osl", + "cycles_integrator", "cycles_util", "cycles_subd", "extern_sky", "extern_cuew" +] + +# It's very weird to link the Cycles static libraries twice, to both +# lib/libGafferCycles.so and python/GafferCycles/_GafferCycles.so, resulting in 2 copies of everything. +# This has caused issues on Ubuntu where the init stuff cycles_session runs twice, terminating Gaffer with an +# exception when the node buffer_pass is registered twice. It seems to work fine on Linux if we don't double +# link it - the symbols from the static library can be included just in libGafferCycles, and the dynamic linker +# will find them OK. +# But on Windows, it seems the symbols from the static libraries aren't made available in libGafferCycles, so +# it wouldn't work to just link them once. It is currently unknown why Windows doesn't have problems with +# running the initialization in cycles_session twice. +# Hopefully this hackery works for now - we're hoping in the long run, Cycles might ship as a dynamic lib, which +# would simplify all this. +includeCyclesLibrariesInPythonModule = env["PLATFORM"] == "win32" + libraries = { "Gaffer" : { @@ -1350,9 +1368,8 @@ libraries = { "LIBPATH" : [ "$CYCLES_ROOT/lib" ], "LIBS" : [ "IECoreScene$CORTEX_LIB_SUFFIX", "IECoreImage$CORTEX_LIB_SUFFIX", "IECoreVDB$CORTEX_LIB_SUFFIX", - "Gaffer", "GafferScene", "GafferDispatch", "GafferOSL", - "cycles_session", "cycles_scene", "cycles_graph", "cycles_bvh", "cycles_device", "cycles_kernel", "cycles_kernel_osl", - "cycles_integrator", "cycles_util", "cycles_subd", "extern_sky", "extern_cuew", + "Gaffer", "GafferScene", "GafferDispatch", "GafferOSL" + ] + cyclesLibraries + [ "OpenImageIO$OIIO_LIB_SUFFIX", "OpenImageIO_Util$OIIO_LIB_SUFFIX", "oslexec$OSL_LIB_SUFFIX", "oslquery$OSL_LIB_SUFFIX", "openvdb$VDB_LIB_SUFFIX", "Alembic", "osdCPU", "OpenColorIO$OCIO_LIB_SUFFIX", "embree4", "Iex", "openpgl", ], @@ -1364,8 +1381,7 @@ libraries = { "LIBPATH" : [ "$CYCLES_ROOT/lib" ], "LIBS" : [ "Gaffer", "GafferScene", "GafferDispatch", "GafferBindings", "GafferCycles", "IECoreScene", - "cycles_session", "cycles_scene", "cycles_graph", "cycles_bvh", "cycles_device", "cycles_kernel", "cycles_kernel_osl", - "cycles_integrator", "cycles_util", "cycles_subd", "extern_sky", "extern_cuew", + ] + ( cyclesLibraries if includeCyclesLibrariesInPythonModule else [] ) + [ "OpenImageIO$OIIO_LIB_SUFFIX", "OpenImageIO_Util$OIIO_LIB_SUFFIX", "oslexec$OSL_LIB_SUFFIX", "openvdb$VDB_LIB_SUFFIX", "oslquery$OSL_LIB_SUFFIX", "Alembic", "osdCPU", "OpenColorIO$OCIO_LIB_SUFFIX", "embree4", "Iex", "openpgl", ], From 7532585220b47bbf0360ffb9df2aba0b4fe442f4 Mon Sep 17 00:00:00 2001 From: Daniel Dresser Date: Tue, 16 Jul 2024 19:14:31 -0700 Subject: [PATCH 10/10] Hacking to get it working again --- src/GafferOSL/ShadingEngine.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/GafferOSL/ShadingEngine.cpp b/src/GafferOSL/ShadingEngine.cpp index 85218e2863..eb04702f8f 100644 --- a/src/GafferOSL/ShadingEngine.cpp +++ b/src/GafferOSL/ShadingEngine.cpp @@ -58,6 +58,9 @@ #include "OSL/oslversion.h" #include "OSL/rendererservices.h" +#undef OSL_USE_BATCHED +#define OSL_USE_BATCHED 0 + #if OSL_USE_BATCHED #include "OSL/batched_shaderglobals.h" #include "OSL/batched_rendererservices.h" @@ -1762,7 +1765,7 @@ struct ExecuteShadeParameters }; -IECore::CompoundDataPtr executeShade( const ExecuteShadeParameters ¶ms, const RenderState &renderState, ShaderGroup &shaderGroup, ShadingSystem *shadingSystem ) +IECore::CompoundDataPtr executeShade( ExecuteShadeParameters ¶ms, const RenderState &renderState, ShaderGroup &shaderGroup, ShadingSystem *shadingSystem, const boost::container::flat_map< OIIO::ustring, int> &gafferTextureIndices ) { // Allocate data for the result ShadingResults results( params.numPoints ); @@ -1771,8 +1774,8 @@ IECore::CompoundDataPtr executeShade( const ExecuteShadeParameters ¶ms, cons // Do a quick init of the shading system while setting the global map of texture indices // to map the correct textures for this ShadingEngine tbb::spin_mutex::scoped_lock lock( g_gafferTextureIndicesMutex ); - g_gafferTextureIndices = &m_gafferTextureIndices; - shadingSystem->execute_init( *params.threadInfoCache.local().shadingContext, shaderGroup, shaderGlobals, false ); + g_gafferTextureIndices = &gafferTextureIndices; + shadingSystem->execute_init( *params.threadInfoCache.local().shadingContext, shaderGroup, params.shaderGlobals, false ); g_gafferTextureIndices = nullptr; } @@ -2027,20 +2030,20 @@ IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points #if OSL_USE_BATCHED if( batchSize == 1 ) { - return executeShade( shadeParameters, renderState, shaderGroup, shadingSystem ); + return executeShade( shadeParameters, renderState, shaderGroup, shadingSystem, m_gafferTextureIndices ); } else if( batchSize == 8 ) { ShadingSystem::BatchedExecutor<8> executor( *shadingSystem ); - return executeShadeBatched<8>( shadeParameters, renderState, shaderGroup, executor ); + return executeShadeBatched<8>( shadeParameters, renderState, shaderGroup, executor, m_gafferTextureIndices ); } else { ShadingSystem::BatchedExecutor<16> executor( *shadingSystem ); - return executeShadeBatched<16>( shadeParameters, renderState, shaderGroup, executor ); + return executeShadeBatched<16>( shadeParameters, renderState, shaderGroup, executor, m_gafferTextureIndices ); } #else - return executeShade( shadeParameters, renderState, shaderGroup, shadingSystem ); + return executeShade( shadeParameters, renderState, shaderGroup, shadingSystem, m_gafferTextureIndices ); #endif }