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:
@@ -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()
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user