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);