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.
This commit is contained in:
Philip Rideout
2022-09-13 16:42:31 -07:00
parent ad6a5eb4e1
commit c4abe4f46a
9 changed files with 88 additions and 32 deletions

View File

@@ -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()

View File

@@ -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];

View File

@@ -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 */

View File

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

View File

@@ -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

View File

@@ -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<Texture*, std::unique_ptr<TextureNode>> mTextureNodes;
std::queue<Entity> mReadyRenderables;
bool mDisabled = false;
};
} // namespace filament::gltfio

View File

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

View File

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

View File

@@ -89,8 +89,8 @@ static void printUsage(char* name) {
" Specify the backend API: opengl (default), vulkan, or metal\n\n"
" --ibl=<path to cmgen IBL>, -i <path>\n"
" Override the built-in IBL\n\n"
" --num=<number of instances>, -n <num>\n"
" Number of instances (defaults to 5)\n\n"
" --num=<number of initial instances>, -n <num>\n"
" Number of instances to start with (defaults to 0)\n\n"
" --animate=<instance index>, -m <num>\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);