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

refactor ResourceManager render assets #935

Conversation

eundersander
Copy link
Contributor

@eundersander eundersander commented Nov 13, 2020

Motivation and Context

This PR is (mostly) a no-op refactor of ResourceManager (no behavior change). The motivation is to unblock the upcoming render replay feature.

I introduce the terms "render asset" and "render asset instance". A render asset is basically a mesh. A render asset can be of type InstanceMesh ("IMesh"), PTex, Primitive, or General (see isRenderAssetGeneral).

A somewhat unrelated change is mixed in here: I'm converting some usage of ResourceKey (a hash) to std::string so that I can better serialize the key.

This is (mostly) a no-op refactor that conceptually replaces loadPTexMeshData / loadInstanceMeshData / loadGeneralMeshData with loadRenderAssetPTex,createRenderAssetInstancePTex / loadRenderAssetIMesh,createRenderAssetInstanceIMesh / loadRenderAssetGeneral,createRenderAssetInstanceGeneralPrimitive.

Some things you can do with render assets and instances:

  • Load a render asset (without adding anything to the scene graph; see loadRenderAsset).
  • Create instances of a render asset (add instances to the scene graph so they show up in observations; see createRenderAssetInstance).
  • Move a render asset instance in the scene (and coming soon, you can hide or unhide it).
  • Delete a render asset instance (remove the instance from the scene graph).
  • Unload a render asset (only after all its instances have been deleted; at present, I think the only way to unload is to reconfigure/reset the simulator).

More details about render assets:

  • Currently, a render asset instance isn't a formal class/object in our code. It's a subtree of scene nodes and drawables. It is a formal object in the serialization code I'm writing for render replay.
  • A PhysicsObject's attributes include its render asset. A PhysicsObject has an associated render asset instance at runtime.
  • A stage has an associated RGBD/"render" render asset instance and optional "semantic" render asset instance.
  • An articulated object will have an associated render asset instance per link at runtime (articulated objects aren't merged to master yet).

Some goals/benefits of this refactor:

  • Cleaner separation between render and simulation code: a replay renderer will be able to load and display stage visuals without calling loadStage, processing stageAttributes, or loading stage collision geometry.
  • unified loading of render assets: loadRenderAsset
  • unified creation of instances: createRenderAssetInstance
  • Users of render assets are (mostly) agnostic to the underlying type: stage, objects, and articulated object links have associated render assets, but they (mostly) don't care what type.
  • Any render asset type can be used for RGBD, Semantic, or both.
  • Where render asset types differ, we're explicit. E.g. explicit asserts that convey that PTex doesn't support scale or lighting.

Some possible future use cases this unlocks (these are side benefits; the main motivation for this PR is unblocking render replay):

  • preload render assets (load prior to use)
  • unload and reload render assets on demand
  • add separate semantic render assets to objects or links
  • use PTex or InstanceMesh render assets with objects and links

How Has This Been Tested

Loading stages with render assets of type PTex, InstanceMesh, and General (GLB), in the viewer
Adding physics objects in the viewer
Testing semantic rendering using a modified semantic_id_tutorial.py
I ran SimTest, which tests adding a primitive

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have completed my CLA (see CONTRIBUTING)
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@facebook-github-bot facebook-github-bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Nov 13, 2020
@@ -180,31 +188,85 @@ bool ResourceManager::loadStage(
((_physicsManager != nullptr) &&
(_physicsManager->getInitializationAttributes()->getSimulator().compare(
"none") != 0));
const Magnum::ResourceKey& renderLightSetup(stageAttributes->getLightSetup());
const std::string renderLightSetupKey(stageAttributes->getLightSetup());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

std::map<std::string, AssetInfo> assetInfoMap =
createStageAssetInfosFromAttributes(stageAttributes, buildCollisionMesh,
loadSemanticMesh);

// set equal to current Simulator::activeSemanticSceneID_ value
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not new code. I've simply moved this semantic loading code to happen earlier in this function. I did this because I need to know if a render asset instance is going to be used for RGBD/Semantic/both at the time of creation. I want to know as early as possible whether we're going to be in "single scene graph" vs "dual scene graph" mode.

RenderAssetInstanceCreation creation{
.filepath = semanticStageFilename,
.scale = Cr::Containers::NullOpt,
.isStatic =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

isStatic roughly corresponds to the previous logic for computeSemanticAABBs.

@Skylion007
Copy link
Contributor

Broke the JS bindings...

} else if (info.type == AssetType::FRL_PTEX_MESH) {
meshSuccess = loadPTexMeshData(info, parent, drawables);
} else if (info.type == AssetType::SUNCG_SCENE) {
if (info.type == AssetType::SUNCG_SCENE) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

SunCG scenes are not render assets.

// Right now, we only allow for an asset to be loaded with one
// configuration, since generated mesh data may be invalid for a new
// configuration
LOG(ERROR)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This logic was moved from loadGeneralMeshData to here.

pTexMeshData->uploadBuffersToGPU(false);

for (int jSubmesh = 0; jSubmesh < pTexMeshData->getSize(); ++jSubmesh) {
scene::SceneNode& node = instanceRoot->createChild();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is one of the few behavior changes in the PR. For the instance mesh, instead of adding all these mesh child sceneNodes directly to the parent sceneNode, I first create an instanceRoot sceneNode. I want every render asset instance to have a single root sceneNode. This is needed for render replay.

I acknowledge there's probably a slight perf cost. Long-term, our new Vulkan batched renderer probably won't use a SceneNode hierarchy, so this shouldn't matter.

scene::SceneNode* instanceRoot = &parent->createChild();

for (uint32_t iMesh = start; iMesh <= end; ++iMesh) {
scene::SceneNode& node = instanceRoot->createChild();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

return false;
DrawableGroup* drawables) {
ASSERT(!creation.scale); // PTex doesn't support scale
ASSERT(creation.lightSetupKey ==
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Explicit asserts to show what a render asset type supports.

} // forceReload
// TODO: cache visual nodes added by this process
std::vector<scene::SceneNode*> visNodeCache;
if (creation.scale) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, instances for objects use scale and instances for stages do not.

@@ -1833,7 +1899,15 @@ bool ResourceManager::loadSUNCGHouseFile(const AssetInfo& houseInfo,
nodeIds.push_back(id);
objectNode.setId(nodeIndex);
if (info.type == AssetType::SUNCG_OBJECT) {
loadGeneralMeshData(info, &objectNode, drawables);
CHECK(loadRenderAsset(info));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This codepath is untested so who knows if it actually works. https://cvmlp.slack.com/archives/G0131KVLBLL/p1604940887000900

* @param parent The @ref scene::SceneNode to which the mesh will be added
* as a child.
* as a child. See also creation->isRGBD and creation->isSemantic. nullptr if
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's an important subtlety here: currently, the way the user chooses RGBD vs Semantic is by passing the appropriate parent and drawables (from either the RGBD or Semantic scene graph). But I'm also requiring the user to specify isRGBD and isSemantic in the creation struct (so that I can record the choice in the replay data).

Eventually, we could refactor this interface so that the user just specifies the creation struct, and then we choose the correct parent and drawables on the backend.

Copy link
Contributor

@aclegg3 aclegg3 left a comment

Choose a reason for hiding this comment

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

Overall looks fine to me. Minor comment that we are using the renderAsset loading pipeline for importing collision proxy assets also. Technically these are render-ready and could be instanced, so I don't think the re-naming is a problem. Just wanted to point this out.

namespace assets {

// parameters to control how a render asset instance is created
struct RenderAssetInstanceCreation {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe clarify that this is an info struct?

Suggested change
struct RenderAssetInstanceCreation {
struct RenderAssetInstanceCreationInfo {

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to rename the file to match the struct?

Copy link
Contributor

@bigbike bigbike left a comment

Choose a reason for hiding this comment

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

@mosra : Can you take another round of review? Thanks!

namespace {
bool isRenderAssetGeneral(AssetType type) {
return type == AssetType::MP3D_MESH || type == AssetType::UNKNOWN ||
type == AssetType::SUNCG_OBJECT;
Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we remove the last one? We should not touch it, or have any "new" code that relatives to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I chatted with Kathryn Duvall from FB legal and she says this will be fine. What I'm trying to do here is not break the SUNCG loading codepath, with the big caveat that we can't test this loading path on SUNCG data or otherwise use SUNCG data. We should consider support for SUNCG data to be deprecated. I will add a runtime warning about this codepath being deprecated.

Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome! Thanks for following up on this!

struct RenderAssetInstanceCreation {
std::string filepath; // see also AssetInfo::filepath
Corrade::Containers::Optional<Magnum::Vector3> scale;
bool isStatic =
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use the Flags instead of a couple of booleans? It is more efficient.
Here is a good example:

enum class Flag : unsigned int {

meshSuccess = loadRenderAssetIMesh(info);
} else if (isRenderAssetGeneral(info.type)) {
meshSuccess = loadRenderAssetGeneral(info);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

CORRADE_INTERNAL_ASSERT_UNREACHABLE() ??
https://doc.magnum.graphics/corrade/Assert_8h.html

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, compared to what's there now, CORRADE_INTERNAL_ASSERT_UNREACHABLE() will give the compiler a correct hint, allowing for better optimizations even in release builds.

DrawableGroup* drawables,
std::vector<scene::SceneNode*>* visNodeCache) {
// assert that asset is already loaded
CHECK(resourceDict_.count(creation.filepath));
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have anything equivalent to the CHECK or more powerful in Corrade? @mosra

Copy link
Collaborator

Choose a reason for hiding this comment

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

How is a CHECK different from an ASSERT? I'd just use CORRADE_ASSERT here.

Copy link
Contributor

Choose a reason for hiding this comment

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

CHECK simply outputs Warning while ASSERT interrupts system running, I believe.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh. But the comment says assert and I feel like it should be an assert :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe CHECK will also abort if the condition is not met. Similar to CORRADE_ASSERT.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. I checked the google docs. Yes, you guys are right. I was wrong.
// CHECK dies with a fatal error if condition is not true.

const auto& info = loadedAssetData.assetInfo;
scene::SceneNode* newNode = nullptr;
if (info.type == AssetType::FRL_PTEX_MESH) {
ASSERT(
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest using CORRADE_ASSERT which is much more powerful and informative.
https://doc.magnum.graphics/corrade/Assert_8h.html

Good example is here:
https://github.com/facebookresearch/habitat-sim/blob/master/src/esp/assets/PTexMeshData.cpp

Search: CORRADE_ASSERT.

return false;
}
} else {
if (resourceDict_[filename].assetInfo != info) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For cases that just output warning, but the system will not be interrupted, do you have similar stuffs in Magnum? @mosra

Copy link
Collaborator

Choose a reason for hiding this comment

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

Mn::Warning{} << bla :) Behaviorally same as Error{} but can be redirected / muted independently of it.

}
// create render asset instance if requested
if (parent) {
ASSERT(creation);
Copy link
Contributor

Choose a reason for hiding this comment

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

CORRADE_ASSERT(...)

Copy link
Collaborator

@mosra mosra left a comment

Choose a reason for hiding this comment

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

I feel a bit like being thrown in a cold water here -- I'm not fully aware of the progress / design / ideas around replay, so I can't really do a thorough review without first accompanying myself with everything related to this work (which I'm not sure I have capacity for, right now).

So all I can offer is random hints and suggestions re Magnum usage :) Hope that's fine.

Oh, and in addition to the comments below basically everything @bigbike says.

.isStatic = true,
.isRGBD = true,
.isSemantic = !isSeparateSemanticScene,
.lightSetupKey = renderLightSetupKey};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Apologies if I'm not up-to-date on C++ standard requirements in Habitat, but designated initializers are standard only since C++20 or require a particular GNU extension, which may hurt portability (especially if we'd want to reconsider Windows support again). I'd personally stay with standard C++ with no compiler-specific extensions.

Same in other places (I spotted one or two more occurence at least).

meshSuccess = loadRenderAssetIMesh(info);
} else if (isRenderAssetGeneral(info.type)) {
meshSuccess = loadRenderAssetGeneral(info);
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, compared to what's there now, CORRADE_INTERNAL_ASSERT_UNREACHABLE() will give the compiler a correct hint, allowing for better optimizations even in release builds.

Comment on lines 488 to 485
ASSERT(false); // createRenderAssetInstance doesn't yet support the
// requested asset type
newNode = nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

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

CORRADE_INTERNAL_ASSERT_UNREACHABLE() here again, and because it marks the code as unreachable, the newNode = nullptr isn't needed to suppress "potentially uninitialized variable" warnings.

return false;
}
} else {
if (resourceDict_[filename].assetInfo != info) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mn::Warning{} << bla :) Behaviorally same as Error{} but can be redirected / muted independently of it.

Copy link
Contributor

@jturner65 jturner65 left a comment

Choose a reason for hiding this comment

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

One minor question (feel free to ignore) otherwise LGTM.

namespace assets {

// parameters to control how a render asset instance is created
struct RenderAssetInstanceCreation {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to rename the file to match the struct?

@eundersander eundersander force-pushed the eundersander/render_asset_instance branch from 0041bbf to 69eebef Compare November 22, 2020 23:45
@Skylion007
Copy link
Contributor

Pull from master to fix pre-commit

@eundersander eundersander force-pushed the eundersander/render_asset_instance branch from c059881 to efcc55c Compare November 25, 2020 03:57
RenderAssetInstanceCreationInfo(
const std::string& _filepath,
const Corrade::Containers::Optional<Magnum::Vector3>& _scale,
bool isStatic,
Copy link
Contributor

@bigbike bigbike Nov 30, 2020

Choose a reason for hiding this comment

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

Because you have defined Flags, these 3 booleans can be replaced by a single argument Flags flags, aren't they? And it will be less error-prone.
That is the purpose to have the Flags introduced.

Copy link
Contributor

@aclegg3 aclegg3 left a comment

Choose a reason for hiding this comment

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

LGTM

@eundersander eundersander merged commit 2aac611 into facebookresearch:master Dec 1, 2020
@eundersander eundersander deleted the eundersander/render_asset_instance branch December 1, 2020 03:50
Ram81 pushed a commit to Ram81/habitat-web-sim that referenced this pull request Dec 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Do not delete this pull request or issue due to inactivity.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants