From c4abe4f46a672b614b7c794db082fe1eee299e8a Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Tue, 13 Sep 2022 16:42:31 -0700 Subject: [PATCH] gltfio: allow zero-instance assets. This is a feature request from Google. It allows users to "preload" an asset, ie you can now create all VertexBuffer objects, Texture objects, etc, without actually creating any entities or renderable components. In the past we used TransformManager to help out with computing the big asset-level bounding box, but now we use `gltf_node_transform_world()` because entities might not yet exist. One minor side effect is that `FilamentAsset::getBoundingBox()` now returns the AABB that was determined at load time, and does not account for instances. As a result, our `gltf_instances` sample app looks slightly different but this is expected. --- RELEASE_NOTES.md | 2 + .../filament/gltfio/FilamentAsset.java | 3 ++ libs/gltfio/include/gltfio/FilamentAsset.h | 15 +++++-- libs/gltfio/src/AssetLoader.cpp | 31 ++++++++------ libs/gltfio/src/DependencyGraph.cpp | 40 +++++++++++++++---- libs/gltfio/src/DependencyGraph.h | 5 +++ libs/gltfio/src/ResourceLoader.cpp | 5 +++ libs/viewer/src/ViewerGui.cpp | 4 +- samples/gltf_instances.cpp | 15 ++++--- 9 files changed, 88 insertions(+), 32 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5a656ce0c4..86197e62cd 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created. ## main branch +- gltfio: allow zero-instance assets + ## v1.27.1 - Java: add methods for TransformManager.getChildCount(), TransformManager.getChildren() and Scene.hasEntity() diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java index a85d1303ef..2f830d38c5 100644 --- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java +++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/FilamentAsset.java @@ -182,6 +182,9 @@ public class FilamentAsset { /** * Gets the bounding box computed from the supplied min / max values in glTF accessors. + * + * This does not return a bounding box over all FilamentInstance, it's just a straightforward + * AAAB that can be determined at load time from the asset data. */ public @NonNull Box getBoundingBox() { float[] box = new float[6]; diff --git a/libs/gltfio/include/gltfio/FilamentAsset.h b/libs/gltfio/include/gltfio/FilamentAsset.h index a8419aafa0..24ed7d9405 100644 --- a/libs/gltfio/include/gltfio/FilamentAsset.h +++ b/libs/gltfio/include/gltfio/FilamentAsset.h @@ -139,6 +139,8 @@ public: * * while (Entity e = popRenderable()) { scene.addEntity(e); } * + * Progressive reveal is not supported for dynamically added instances. + * * \see ResourceLoader#asyncBeginLoad * \see popRenderables() */ @@ -170,7 +172,12 @@ public: /** Gets the number of resource URIs returned by getResourceUris(). */ size_t getResourceUriCount() const noexcept; - /** Gets the bounding box computed from the supplied min / max values in glTF accessors. */ + /** + * Gets the bounding box computed from the supplied min / max values in glTF accessors. + * + * This does not return a bounding box over all FilamentInstance, it's just a straightforward + * AAAB that can be determined at load time from the asset data. + */ filament::Aabb getBoundingBox() const noexcept; /** Gets the NameComponentManager label for the given entity, if it exists. */ @@ -319,9 +326,11 @@ public: void detachMaterialInstances(); /** - * Convenience function to get the first instance (which always exists). + * Convenience function to get the first instance, or null if it doesn't exist. */ - FilamentInstance* getInstance() noexcept { return getAssetInstances()[0]; } + FilamentInstance* getInstance() noexcept { + return getAssetInstanceCount() > 0 ? getAssetInstances()[0] : nullptr; + } /*! \cond PRIVATE */ diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 878e3b938b..75d7c0d84a 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -279,8 +279,6 @@ FFilamentAsset* FAssetLoader::createAsset(const uint8_t* bytes, uint32_t byteCou FFilamentAsset* FAssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_t byteCount, FilamentInstance** instances, size_t numInstances) { - ASSERT_PRECONDITION(numInstances > 0, "Instance count must be 1 or more."); - // This method can be used to load JSON or GLB. By using a default options struct, we are asking // cgltf to examine the magic identifier to determine which type of file is being loaded. cgltf_options options {}; @@ -510,7 +508,11 @@ FFilamentInstance* FAssetLoader::createInstance(const cgltf_data* srcAsset) { // This needs to stay near the end of the method to allow detection of the first instance. mAsset->mInstances.push_back(instance); + // Bounding boxes are not shared because users might call recomputeBoundingBoxes() which can + // be affected by entity transforms. However, upon instance creation we can safely copy over + // the asset's bounding box. instance->boundingBox = mAsset->mBoundingBox; + return instance; } @@ -596,6 +598,9 @@ void FAssetLoader::createPrimitives(const cgltf_data* srcAsset, const cgltf_node prims.reserve(mesh->primitives_count); prims.resize(mesh->primitives_count); } + + Aabb aabb; + for (cgltf_size index = 0, n = mesh->primitives_count; index < n; ++index) { Primitive& outputPrim = prims[index]; const cgltf_primitive& inputPrim = mesh->primitives[index]; @@ -605,17 +610,23 @@ void FAssetLoader::createPrimitives(const cgltf_data* srcAsset, const cgltf_node mError = true; return; } + + // Expand the object-space bounding box. + aabb.min = min(outputPrim.aabb.min, aabb.min); + aabb.max = max(outputPrim.aabb.max, aabb.max); } + + mat4f worldTransform; + cgltf_node_transform_world(node, &worldTransform[0][0]); + + const Aabb transformed = aabb.transform(worldTransform); + mAsset->mBoundingBox.min = min(mAsset->mBoundingBox.min, transformed.min); + mAsset->mBoundingBox.max = max(mAsset->mBoundingBox.max, transformed.max); } void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node* node, Entity entity, const char* name) { const cgltf_mesh* mesh = node->mesh; - - // Compute the transform relative to the root. - auto thisTransform = mTransformManager.getInstance(entity); - mat4f worldTransform = mTransformManager.getWorldTransform(thisTransform); - const cgltf_size primitiveCount = mesh->primitives_count; // If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer objects @@ -687,12 +698,6 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node auto& nm = mNodeManager; nm.setMorphTargetNames(nm.getInstance(entity), std::move(morphTargetNames)); - const Aabb transformed = aabb.transform(worldTransform); - - // Expand the world-space bounding box. - mAsset->mBoundingBox.min = min(mAsset->mBoundingBox.min, transformed.min); - mAsset->mBoundingBox.max = max(mAsset->mBoundingBox.max, transformed.max); - if (node->skin) { builder.skinning(node->skin->joints_count); } diff --git a/libs/gltfio/src/DependencyGraph.cpp b/libs/gltfio/src/DependencyGraph.cpp index 0a44a6f415..7512e0ae74 100644 --- a/libs/gltfio/src/DependencyGraph.cpp +++ b/libs/gltfio/src/DependencyGraph.cpp @@ -36,8 +36,15 @@ size_t DependencyGraph::popRenderables(Entity* result, size_t count) noexcept { } void DependencyGraph::addEdge(Entity entity, MaterialInstance* mi) { - mMaterialToEntity[mi].insert(entity); - mEntityToMaterial[entity].materials.insert(mi); + if (mDisabled) { + if (mEntityToMaterial.count(entity) == 0) { + mEntityToMaterial[entity] = {}; + mReadyRenderables.push(entity); + } + } else { + mMaterialToEntity[mi].insert(entity); + mEntityToMaterial[entity].materials.insert(mi); + } } void DependencyGraph::addEdge(MaterialInstance* mi, const char* parameter) { @@ -62,8 +69,10 @@ void DependencyGraph::commitEdges() { void DependencyGraph::addEdge(Texture* texture, MaterialInstance* material, const char* parameter) { assert_invariant(texture); - mTextureToMaterial[texture].insert(material); - mMaterialToTexture.at(material).params.at(parameter) = getStatus(texture); + if (!mDisabled) { + mTextureToMaterial[texture].insert(material); + mMaterialToTexture.at(material).params.at(parameter) = getStatus(texture); + } } void DependencyGraph::checkReadiness(Material* material) { @@ -86,7 +95,11 @@ void DependencyGraph::checkReadiness(Material* material) { void DependencyGraph::markAsReady(Texture* texture) { assert_invariant(texture); - mTextureNodes.at(texture)->ready = true; + auto iter = mTextureNodes.find(texture); + if (iter == mTextureNodes.end()) { + return; + } + iter.value()->ready = true; // Iterate over the materials associated with this texture to check if any have become ready. // This is O(n2) but the inner loop is always small. @@ -97,8 +110,12 @@ void DependencyGraph::markAsReady(Texture* texture) { } void DependencyGraph::markAsReady(MaterialInstance* material) { - auto& entities = mMaterialToEntity.at(material); - for (auto entity : entities) { + auto iter = mMaterialToEntity.find(material); + if (iter == mMaterialToEntity.end()) { + // It's fine if no entities exist yet. + return; + } + for (auto entity : iter->second) { auto& status = mEntityToMaterial.at(entity); assert_invariant(status.numReadyMaterials <= status.materials.size()); if (status.numReadyMaterials == status.materials.size()) { @@ -121,4 +138,13 @@ DependencyGraph::TextureNode* DependencyGraph::getStatus(Texture* texture) { return iter->second.get(); } +void DependencyGraph::disableProgressiveReveal() { + mDisabled = true; + for (auto& [entity, status] : mEntityToMaterial) { + if (status.numReadyMaterials < status.materials.size()) { + mReadyRenderables.push(entity); + } + } +} + } // namespace filament::gltfio diff --git a/libs/gltfio/src/DependencyGraph.h b/libs/gltfio/src/DependencyGraph.h index f060021119..00cd4159bb 100644 --- a/libs/gltfio/src/DependencyGraph.h +++ b/libs/gltfio/src/DependencyGraph.h @@ -79,6 +79,10 @@ public: // Marks the given texture as being fully decoded, with all miplevels initialized. void markAsReady(Texture* texture); + // Causes the dependency graph to enter a disabled state, whereby adding Entity <=> Material + // edges will immediately mark the entity as ready without actually growing the graph. + void disableProgressiveReveal(); + private: struct TextureNode { Texture* texture; @@ -110,6 +114,7 @@ private: tsl::robin_map> mTextureNodes; std::queue mReadyRenderables; + bool mDisabled = false; }; } // namespace filament::gltfio diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 1358a8fb59..9c566e4342 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -331,6 +331,11 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { } asset->mResourcesLoaded = true; + // At this point, any entities that are created in the future (i.e. dynamically added instances) + // will not need the progressive feature to be enabled. This simplifies the dependency graph and + // prevents it from growing. + asset->mDependencyGraph.disableProgressiveReveal(); + // Clear our texture caches. Previous calls to loadResources may have populated these, but the // Texture objects could have since been destroyed. pImpl->mBufferTextureCache.clear(); diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index 57bae6d2e4..d7f5fd2e34 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -457,7 +457,9 @@ void ViewerGui::updateRootTransform() { auto root = tcm.getInstance(mAsset->getRoot()); filament::math::mat4f transform; if (mSettings.viewer.autoScaleEnabled) { - transform = fitIntoUnitCube(mAsset->getInstance()->getBoundingBox(), 4); + FilamentInstance* instance = mAsset->getInstance(); + Aabb aabb = instance ? instance->getBoundingBox() : mAsset->getBoundingBox(); + transform = fitIntoUnitCube(aabb, 4); } tcm.setTransform(root, transform); } diff --git a/samples/gltf_instances.cpp b/samples/gltf_instances.cpp index db2963df94..08ef683a68 100644 --- a/samples/gltf_instances.cpp +++ b/samples/gltf_instances.cpp @@ -89,8 +89,8 @@ static void printUsage(char* name) { " Specify the backend API: opengl (default), vulkan, or metal\n\n" " --ibl=, -i \n" " Override the built-in IBL\n\n" - " --num=, -n \n" - " Number of instances (defaults to 5)\n\n" + " --num=, -n \n" + " Number of instances to start with (defaults to 0)\n\n" " --animate=, -m \n" " Instance to animate (defaults to all instances)\n\n" " --ubershader, -u\n" @@ -148,9 +148,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { break; } } - if (app->instances.empty()) { - app->instances.resize(5); - } return optind; } @@ -248,7 +245,7 @@ int main(int argc, char** argv) { auto setup = [&](Engine* engine, View* view, Scene* scene) { app.engine = engine; app.names = new NameComponentManager(EntityManager::get()); - app.viewer = new ViewerGui(engine, scene, view, app.instanceToAnimate); + app.viewer = new ViewerGui(engine, scene, view); app.materials = (app.materialSource == JITSHADER) ? createJitShaderProvider(engine) : createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE); @@ -262,8 +259,10 @@ int main(int argc, char** argv) { loadAsset(filename); } - FilamentInstance* const instance = app.instanceToAnimate > -1 ? - app.instances[app.instanceToAnimate] : nullptr; + FilamentInstance* instance = nullptr; + if (app.instanceToAnimate > -1 && app.instanceToAnimate < app.instances.size()) { + instance = app.instances[app.instanceToAnimate]; + } arrangeIntoCircle(); loadResources(filename);