Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAYA-123361 fix unshared stage toggling and loading #2410

Merged
merged 3 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/mayaUsd/fileio/jobs/readJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ bool UsdMaya_ReadJob::Read(std::vector<MDagPath>* addedDagPaths)
mImportData.stageInitialLoadSet());
} else {
UsdStageCacheContext stageCacheContext(UsdMayaStageCache::Get(
mImportData.stageInitialLoadSet() == UsdStage::InitialLoadSet::LoadAll));
mImportData.stageInitialLoadSet(), UsdMayaStageCache::ShareMode::Shared));
if (mArgs.pullImportStage)
stage = mArgs.pullImportStage;
else
Expand Down
148 changes: 125 additions & 23 deletions lib/mayaUsd/nodes/proxyShapeBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,9 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
}

// Normal context computation
UsdStageRefPtr usdStage;
UsdStageRefPtr sharedUsdStage;
UsdStageRefPtr unsharedUsdStage;
UsdStageRefPtr finalUsdStage;
SdfPath primPath;

MDataHandle inDataHandle = dataBlock.inputValue(inStageDataAttr, &retValue);
Expand Down Expand Up @@ -711,7 +713,7 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
if (!inDataHandle.data().isNull()) {
MayaUsdStageData* inStageData
= dynamic_cast<MayaUsdStageData*>(inDataHandle.asPluginData());
usdStage = inStageData->stage;
sharedUsdStage = inStageData->stage;
primPath = inStageData->primPath;
isIncomingStage = true;
} else {
Expand All @@ -721,7 +723,7 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
const auto cacheId = UsdStageCache::Id::FromLongInt(cacheIdNum);
const auto stageCached = cacheId.IsValid() && UsdUtilsStageCache::Get().Contains(cacheId);
if (stageCached) {
usdStage = UsdUtilsStageCache::Get().Find(cacheId);
sharedUsdStage = UsdUtilsStageCache::Get().Find(cacheId);
isIncomingStage = true;
} else {
//
Expand Down Expand Up @@ -778,14 +780,19 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
// UsdStage. See https://github.com/Autodesk/maya-usd/issues/528 for
// more information.
UsdStageCacheContext ctx(
UsdMayaStageCache::Get(loadSet == UsdStage::InitialLoadSet::LoadAll));
UsdMayaStageCache::Get(loadSet, UsdMayaStageCache::ShareMode::Shared));

SdfLayerRefPtr rootLayer
= sharableStage ? computeRootLayer(dataBlock, fileString) : nullptr;
if (nullptr == rootLayer)
rootLayer = SdfLayer::FindOrOpen(fileString);

if (rootLayer) {
// Note: computeSessionLayer will find a session layer *only* if the
// Maya scene had been saved and thus serialized the session
// layer. Otherwise it returns null which will mean to use
// whatever session layer happens to be associated with the
// stage we potentially find in the stage cache.
SdfLayerRefPtr sessionLayer = computeSessionLayer(dataBlock);

MProfilingScope profilingScope(
Expand All @@ -798,34 +805,47 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
= MGlobal::optionVarIntValue(kSessionLayerOptionVarName) == 1;
targetSession = targetSession || !rootLayer->PermissionToEdit();

if (sessionLayer || targetSession) {
if (!sessionLayer)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When targeting the session layer without a named session layer loaded from Maya scene, it would create a new session layer. This would prevent reusing a previously shared stage. Target session vs stage sharing are orthogonal. I looked at the original github issue and PR and the goal was to be able to target the session, with no discussion about affecting stage sharing.

I think the code was unwittingly written to have that side effect, so I fixed it.

sessionLayer = SdfLayer::CreateAnonymous();
usdStage = UsdStage::Open(
// Note: UsdStage::Open has the peculiar design that it will return
// any previously open stage that happen to match its arguments,
// all its arguments, but only those arguments.
//
// So *not* passing in a session layer will find any stage that
// has the given root layer. That is why it is important *not* to
// pass the session layer if the session layer is null. Otherwise
// the cache would try find a stage *without* a session layer.
//
// So, not passing the (null) session layer is how a newly-created
// shared stage with the same root layer will find the correct stage
// with the existing session layer.
//
// If the stage is not in the cache and no session layer is passed
// then UsdStage::Open will create the in-memory session layer for us,
// just as we want.
if (sessionLayer) {
sharedUsdStage = UsdStage::Open(
rootLayer,
sessionLayer,
ArGetResolver().CreateDefaultContextForAsset(fileString),
loadSet);
} else {
usdStage = UsdStage::Open(
sharedUsdStage = UsdStage::Open(
rootLayer,
ArGetResolver().CreateDefaultContextForAsset(fileString),
loadSet);
}
if (sessionLayer && targetSession) {
usdStage->SetEditTarget(sessionLayer);
} else {
usdStage->SetEditTarget(usdStage->GetRootLayer());
}

sharedUsdStage->SetEditTarget(
targetSession ? sharedUsdStage->GetSessionLayer()
: sharedUsdStage->GetRootLayer());
} else {
// Create a new stage in memory with an anonymous root layer.
usdStage = UsdStage::CreateInMemory(kAnonymousLayerName, loadSet);
sharedUsdStage = UsdStage::CreateInMemory(kAnonymousLayerName, loadSet);
}
}
}
}

if (!usdStage) {
if (!sharedUsdStage) {
return MS::kFailure;
}

Expand All @@ -838,7 +858,7 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
CHECK_MSTATUS_AND_RETURN_IT(retValue);

if (isIncomingStage) {
std::vector<std::string> incomingLayers { usdStage->GetRootLayer()->GetIdentifier() };
std::vector<std::string> incomingLayers { sharedUsdStage->GetRootLayer()->GetIdentifier() };
_incomingLayers = UsdMayaUtil::getAllSublayers(incomingLayers, true);
} else {
_incomingLayers.clear();
Expand All @@ -859,10 +879,11 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
}
}
}
finalUsdStage = sharedUsdStage;
}
// Own the stage
else {
SdfLayerRefPtr inRootLayer = usdStage->GetRootLayer();
SdfLayerRefPtr inRootLayer = sharedUsdStage->GetRootLayer();

if (!_unsharedStageRootLayer) {
_unsharedStageRootLayer = SdfLayer::CreateAnonymous(kUnsharedStageLayerName);
Expand Down Expand Up @@ -903,16 +924,18 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
newReferencedLayers, _unsharedStageRootLayer, MayaUsdMetadata->ReferencedLayers);
}

usdStage = UsdStage::UsdStage::Open(_unsharedStageRootLayer, loadSet);
unsharedUsdStage = getUnsharedStage(loadSet);
finalUsdStage = unsharedUsdStage;
}

if (usdStage) {
primPath = usdStage->GetPseudoRoot().GetPath();
copyLoadRulesFromAttribute(thisMObject(), *usdStage);
if (finalUsdStage) {
primPath = finalUsdStage->GetPseudoRoot().GetPath();
copyLoadRulesFromAttribute(thisMObject(), *finalUsdStage);
updateShareMode(sharedUsdStage, unsharedUsdStage, loadSet);
}

// Set the outUsdStageData
stageData->stage = usdStage;
stageData->stage = finalUsdStage;
stageData->primPath = primPath;

// Set the data on the output plug
Expand All @@ -925,6 +948,83 @@ MStatus MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock)
return MS::kSuccess;
}

UsdStageRefPtr MayaUsdProxyShapeBase::getUnsharedStage(UsdStage::InitialLoadSet loadSet)
{
// The unshared stages are *also* kept in a stage cache so that we can find them
// again when proxy shape attribute change. For example, if the 'loadPayloads'
// attribute change, we want to find the same unshared stage, we don't want to lose
// edits, in particular in its session layer.
//
// We also need to be able to find them when switching a stage between non-shared
// and shared, so that we can transfer the content of the session layer.
//
// Fortunately, the USD stage cache matches stages using *all* arguments provided.
// So if the unshared session layer is unique to this proxy shape, there is no
// chance of finding it by accident from another proxy shape.
UsdStageCacheContext ctx(
UsdMayaStageCache::Get(loadSet, UsdMayaStageCache::ShareMode::Unshared));

if (!_unsharedStageSessionLayer)
_unsharedStageSessionLayer = SdfLayer::CreateAnonymous();

return UsdStage::UsdStage::Open(_unsharedStageRootLayer, _unsharedStageSessionLayer, loadSet);
}

void MayaUsdProxyShapeBase::updateShareMode(
const UsdStageRefPtr& sharedUsdStage,
const UsdStageRefPtr& unsharedUsdStage,
UsdStage::InitialLoadSet loadSet)
{
// Based on the previous shared mode and current shared mode of the stage,
// transfer the content of the session layer from one to the other as needed.
const auto shareMode = isShareableStage() ? ShareMode::Shared : ShareMode::Unshared;
if (shareMode == _previousShareMode)
return;

Copy link
Collaborator Author

@pierrebai-adsk pierrebai-adsk Jun 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On thing I was not sure of: do I need to reset the previous share mode in some cases? For example, if the user edits the filename, would we need to reset the flag? Where and when, through what mechanism would that be done?

I did not see that any other member variable get reset in these cases, so I did not do anything about it for this flag.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resetting the share mode on file (i.e. root layer) change is an interesting question. I think you'll need to follow up with Design here so that we can schedule some time for this.

// Only transfer the session content if the previous mode was known
// or if the current mode is unshared, as the session layer content
// is put in the shared stage when loaded from disk in a Maya scene.
//
// IOW:
// Shared -> Unshared : copy
// Unshared -> Shared : copy
// Unknown -> Unshared : copy
//
// Shared -> Shared : content already in place, pruned above
// Unshared -> Unshared : content already in place, pruned above
// Unknown -> Shared : content already in place
//
// X -> Unknown : impossible since the new mode is always known
if (_previousShareMode != ShareMode::Unknown || shareMode == ShareMode::Unshared)
transferSessionLayer(shareMode, sharedUsdStage, unsharedUsdStage, loadSet);

_previousShareMode = shareMode;
}

void MayaUsdProxyShapeBase::transferSessionLayer(
ShareMode currentMode,
const UsdStageRefPtr& sharedUsdStage,
const UsdStageRefPtr& unsharedUsdStage,
UsdStage::InitialLoadSet loadSet)
{
// When flipping to shared from unshared, the unshared set was not loaded.
// Load it now to be able to transfer the session layer content.
UsdStageRefPtr validUnsharedUsdStage
= unsharedUsdStage ? unsharedUsdStage : getUnsharedStage(loadSet);

SdfLayerHandle sharedSession = sharedUsdStage->GetSessionLayer();
SdfLayerHandle unsharedSession = validUnsharedUsdStage->GetSessionLayer();

if (!sharedSession || !unsharedSession)
return;

if (currentMode == ShareMode::Shared) {
sharedSession->TransferContent(unsharedSession);
} else {
unsharedSession->TransferContent(sharedSession);
}
}

MStatus MayaUsdProxyShapeBase::computeOutStageData(MDataBlock& dataBlock)
{
MProfilingScope computeOutStageDatacomputeOutStageData(
Expand Down Expand Up @@ -1618,6 +1718,8 @@ MayaUsdProxyShapeBase::MayaUsdProxyShapeBase(
const bool useLoadRulesHandling)
: MPxSurfaceShape()
, _isUfeSelectionEnabled(enableUfeSelection)
, _previousShareMode(ShareMode::Unknown)
, _unsharedStageSessionLayer(nullptr)
, _unsharedStageRootLayer(nullptr)
, _unsharedStageRootSublayers()
, _incomingLayers()
Expand Down
27 changes: 27 additions & 0 deletions lib/mayaUsd/nodes/proxyShapeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,15 @@ class MayaUsdProxyShapeBase
void copyInternalData(MPxNode* srcNode) override;

private:
// The possible the shared mode of the stage.
// The 'Unknown' mode is when the proxy shape is created and has not yet been computed.
enum class ShareMode
{
Unknown,
Shared,
Unshared
};

MayaUsdProxyShapeBase(const MayaUsdProxyShapeBase&);
MayaUsdProxyShapeBase& operator=(const MayaUsdProxyShapeBase&);

Expand All @@ -347,6 +356,19 @@ class MayaUsdProxyShapeBase
MStatus computeOutStageData(MDataBlock& dataBlock);
MStatus computeOutStageCacheId(MDataBlock& dataBlock);

void updateShareMode(
const UsdStageRefPtr& sharedUsdStage,
const UsdStageRefPtr& unsharedUsdStage,
UsdStage::InitialLoadSet loadSet);

void transferSessionLayer(
ShareMode currentMode,
const UsdStageRefPtr& sharedUsdStage,
const UsdStageRefPtr& unsharedUsdStage,
UsdStage::InitialLoadSet loadSet);

UsdStageRefPtr getUnsharedStage(UsdStage::InitialLoadSet loadSet);

SdfPathVector _GetExcludePrimPaths(MDataBlock dataBlock) const;
int _GetComplexity(MDataBlock dataBlock) const;
UsdTimeCode _GetTime(MDataBlock dataBlock) const;
Expand All @@ -373,7 +395,12 @@ class MayaUsdProxyShapeBase
// Whether or not the proxy shape has enabled UFE/subpath selection
const bool _isUfeSelectionEnabled;

// Track the shared mode of the stage as seen in the last compute.
// Starts off as Unknown when the proxy shape is first created.
ShareMode _previousShareMode { ShareMode::Unknown };

// For unshared composition
SdfLayerRefPtr _unsharedStageSessionLayer;
SdfLayerRefPtr _unsharedStageRootLayer;

// We need to keep track of unshared sublayers (otherwise they get removed)
Expand Down
21 changes: 13 additions & 8 deletions lib/mayaUsd/nodes/stageNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,24 @@ MStatus UsdMayaStageNode::compute(const MPlug& plug, MDataBlock& dataBlock)
if (SdfLayerRefPtr rootLayer = SdfLayer::FindOrOpen(usdFile)) {
static const MString kSessionLayerOptionVarName(
MayaUsdOptionVars->ProxyTargetsSessionLayerOnOpen.GetText());
const bool loadAll = true;
UsdStageCacheContext ctx(UsdMayaStageCache::Get(loadAll));
UsdStageCacheContext ctx(UsdMayaStageCache::Get(
UsdStage::InitialLoadSet::LoadAll, UsdMayaStageCache::ShareMode::Shared));

// Note: UsdStage::Open uses all its argument to match an existing stage
// in the cache, including the sesssion layer if passed explicitly.
// So do *not* pass a newly-created session layer in as would create
// another stage instead of re-using the existing one.
//
// If the stage is not in the cache and no session layer is passed
// then UsdStage::Open will create the in-memory session layer for us.
usdStage = UsdStage::Open(rootLayer, ArGetResolver().GetCurrentContext());

bool targetSession = MGlobal::optionVarIntValue(kSessionLayerOptionVarName) == 1;
targetSession = targetSession || !rootLayer->PermissionToEdit();

if (targetSession) {
SdfLayerRefPtr sessionLayer = SdfLayer::CreateAnonymous();
usdStage
= UsdStage::Open(rootLayer, sessionLayer, ArGetResolver().GetCurrentContext());
SdfLayerRefPtr sessionLayer = usdStage->GetSessionLayer();
if (sessionLayer && targetSession) {
usdStage->SetEditTarget(sessionLayer);
} else {
usdStage = UsdStage::Open(rootLayer, ArGetResolver().GetCurrentContext());
usdStage->SetEditTarget(usdStage->GetRootLayer());
}
}
Expand Down
15 changes: 13 additions & 2 deletions lib/mayaUsd/python/wrapStageCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <pxr/base/tf/pyResultConversions.h>
#include <pxr/pxr.h>

#include <boost/python.hpp>
#include <boost/python/args.hpp>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
Expand All @@ -28,14 +29,24 @@ using namespace boost;

PXR_NAMESPACE_USING_DIRECTIVE

namespace {
UsdStageCache& _UsdMayaStageCacheGet(bool loadAll, bool shared)
{
return UsdMayaStageCache::Get(
loadAll ? UsdStage::InitialLoadSet::LoadAll : UsdStage::InitialLoadSet::LoadNone,
shared ? UsdMayaStageCache::ShareMode::Shared : UsdMayaStageCache::ShareMode::Unshared);
}

} // namespace

void wrapStageCache()
{
class_<UsdMayaStageCache>("StageCache")

.def(
"Get",
&UsdMayaStageCache::Get,
args("loadAll"),
_UsdMayaStageCacheGet,
python::args("loadAll", "shared"),
return_value_policy<reference_existing_object>())
.staticmethod("Get")
.def("Clear", &UsdMayaStageCache::Clear)
Expand Down
Loading