From 9031babd922e792833fe74be289c5b9a9973dacb Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Tue, 3 May 2022 15:18:58 -0700 Subject: [PATCH] Various glTF-related cleanup and enhancements. - For completion, FilamentAsset now has getRenderableEntities(). This is similar to sister methods getLightEntities() and getCameraEntities() except there is no need to store a separate array. - The web helmet demo does not need to enable shadows, they are already enabled. - The ViewerGui populateAsset() method was doing two things that are now decoupled for clarity: setAsset() and populateAsset(). - The updateRootTransform() method is now called only when the autoscale checkbox is toggled, instead of every frame. - The getFooEntities() methods in Java now skip doing work for empty lists. Actually these should not return arrays at all, but let's fix that later, since it will break backwards compatibility. --- .../src/main/cpp/FilamentAsset.cpp | 39 ++++++++++++++++--- .../filament/gltfio/FilamentAsset.java | 12 ++++++ libs/gltfio/include/gltfio/FilamentAsset.h | 10 +++++ libs/gltfio/src/AssetLoader.cpp | 12 ++++++ libs/gltfio/src/FFilamentAsset.h | 11 +++++- libs/gltfio/src/FilamentAsset.cpp | 8 ++++ libs/viewer/include/viewer/ViewerGui.h | 14 ++++++- libs/viewer/src/ViewerGui.cpp | 9 +++-- samples/gltf_instances.cpp | 10 ++--- samples/gltf_viewer.cpp | 5 ++- web/filament-js/extensions.js | 4 ++ web/filament-js/filament.d.ts | 1 + web/filament-js/jsbindings.cpp | 5 +++ web/samples/helmet.html | 11 +----- 14 files changed, 122 insertions(+), 29 deletions(-) diff --git a/android/gltfio-android/src/main/cpp/FilamentAsset.cpp b/android/gltfio-android/src/main/cpp/FilamentAsset.cpp index f4c2fa20f1..37aa3cfa17 100644 --- a/android/gltfio-android/src/main/cpp/FilamentAsset.cpp +++ b/android/gltfio-android/src/main/cpp/FilamentAsset.cpp @@ -120,10 +120,34 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_gltfio_FilamentAsset_nGetLightEntities(JNIEnv* env, jclass, jlong nativeAsset, jintArray result) { FilamentAsset* asset = (FilamentAsset*) nativeAsset; - jsize available = env->GetArrayLength(result); + const jsize available = env->GetArrayLength(result); + const size_t minCount = std::min(available, (jsize) asset->getLightEntityCount()); + if (minCount == 0) { + return; + } Entity* entities = (Entity*) env->GetIntArrayElements(result, nullptr); - std::copy_n(asset->getLightEntities(), - std::min(available, (jsize) asset->getLightEntityCount()), entities); + std::copy_n(asset->getLightEntities(), minCount, entities); + env->ReleaseIntArrayElements(result, (jint*) entities, 0); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_gltfio_FilamentAsset_nGetRenderableEntityCount(JNIEnv*, jclass, + jlong nativeAsset) { + FilamentAsset* asset = (FilamentAsset*) nativeAsset; + return asset->getRenderableEntityCount(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_gltfio_FilamentAsset_nGetRenderableEntities(JNIEnv* env, jclass, + jlong nativeAsset, jintArray result) { + FilamentAsset* asset = (FilamentAsset*) nativeAsset; + const jsize available = env->GetArrayLength(result); + const size_t minCount = std::min(available, (jsize) asset->getRenderableEntityCount()); + if (minCount == 0) { + return; + } + Entity* entities = (Entity*) env->GetIntArrayElements(result, nullptr); + std::copy_n(asset->getRenderableEntities(), minCount, entities); env->ReleaseIntArrayElements(result, (jint*) entities, 0); } @@ -131,10 +155,13 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_gltfio_FilamentAsset_nGetCameraEntities(JNIEnv* env, jclass, jlong nativeAsset, jintArray result) { FilamentAsset* asset = (FilamentAsset*) nativeAsset; - jsize available = env->GetArrayLength(result); + const jsize available = env->GetArrayLength(result); + const size_t minCount = std::min(available, (jsize) asset->getCameraEntityCount()); + if (minCount == 0) { + return; + } Entity* entities = (Entity*) env->GetIntArrayElements(result, nullptr); - std::copy_n(asset->getCameraEntities(), - std::min(available, (jsize) asset->getCameraEntityCount()), entities); + std::copy_n(asset->getCameraEntities(), minCount, entities); env->ReleaseIntArrayElements(result, (jint*) entities, 0); } 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 47c1c6bed8..747db67333 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 @@ -113,6 +113,15 @@ public class FilamentAsset { return result; } + /** + * Gets only the entities that have renderable components. + */ + public @NonNull @Entity int[] getRenderableEntities() { + int[] result = new int[nGetRenderableEntityCount(mNativeObject)]; + nGetRenderableEntities(mNativeObject, result); + return result; + } + /** * Gets only the entities that have camera components. * @@ -321,6 +330,9 @@ public class FilamentAsset { private static native int nGetLightEntityCount(long nativeAsset); private static native void nGetLightEntities(long nativeAsset, int[] result); + private static native int nGetRenderableEntityCount(long nativeAsset); + private static native void nGetRenderableEntities(long nativeAsset, int[] result); + private static native int nGetCameraEntityCount(long nativeAsset); private static native void nGetCameraEntities(long nativeAsset, int[] result); diff --git a/libs/gltfio/include/gltfio/FilamentAsset.h b/libs/gltfio/include/gltfio/FilamentAsset.h index 6078aa6cff..b6f4c2ae7c 100644 --- a/libs/gltfio/include/gltfio/FilamentAsset.h +++ b/libs/gltfio/include/gltfio/FilamentAsset.h @@ -77,6 +77,16 @@ public: */ size_t getLightEntityCount() const noexcept; + /** + * Gets the list of entities in the asset that have renderable components. + */ + const utils::Entity* getRenderableEntities() const noexcept; + + /** + * Gets the number of entities returned by getRenderableEntities(). + */ + size_t getRenderableEntityCount() const noexcept; + /** * Gets the list of entities in the scene representing cameras. All of these have a \c Camera * component. diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 327acbb47f..6beb6b09cb 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -299,6 +299,8 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances) } } + mResult->mRenderableCount = 0; + if (numInstances == 0) { // For each scene root, recursively create all entities. for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) { @@ -317,6 +319,14 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances) } } + // Sort the entities so that the renderable ones come first. This allows us to expose + // a "renderables only" pointer without storing a separate list. + const auto& rm = mEngine->getRenderableManager(); + std::sort(mResult->mEntities.begin(), mResult->mEntities.end(), + [&rm](const Entity& a, const Entity& b) { + return rm.hasComponent(a); + }); + // Find every unique resource URI and store a pointer to any of the cgltf-owned cstrings // that match the URI. These strings get freed during releaseSourceData(). tsl::robin_map resourceUris; @@ -564,6 +574,8 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node } mRenderableManager.setMorphWeights(renderable, weights.data(), size); } + + ++mResult->mRenderableCount; } void FAssetLoader::createMaterialVariants(const cgltf_data* srcAsset, const cgltf_mesh* mesh, diff --git a/libs/gltfio/src/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index 334cf4d0df..0c78e5688a 100644 --- a/libs/gltfio/src/FFilamentAsset.h +++ b/libs/gltfio/src/FFilamentAsset.h @@ -139,6 +139,14 @@ struct FFilamentAsset : public FilamentAsset { return mLightEntities.size(); } + const utils::Entity* getRenderableEntities() const noexcept { + return (mRenderableCount == 0) ? nullptr : mEntities.data(); + } + + size_t getRenderableEntityCount() const noexcept { + return mRenderableCount; + } + const utils::Entity* getCameraEntities() const noexcept { return mCameraEntities.empty() ? nullptr : mCameraEntities.data(); } @@ -250,9 +258,10 @@ struct FFilamentAsset : public FilamentAsset { filament::Engine* mEngine; utils::NameComponentManager* mNameManager; utils::EntityManager* mEntityManager; - std::vector mEntities; + std::vector mEntities; // sorted such that renderables come first std::vector mLightEntities; std::vector mCameraEntities; + size_t mRenderableCount = 0; std::vector mMaterialInstances; std::vector mVertexBuffers; std::vector mBufferObjects; diff --git a/libs/gltfio/src/FilamentAsset.cpp b/libs/gltfio/src/FilamentAsset.cpp index d61892ef2a..4c97aa77c1 100644 --- a/libs/gltfio/src/FilamentAsset.cpp +++ b/libs/gltfio/src/FilamentAsset.cpp @@ -280,6 +280,14 @@ size_t FilamentAsset::getLightEntityCount() const noexcept { return upcast(this)->getLightEntityCount(); } +const Entity* FilamentAsset::getRenderableEntities() const noexcept { + return upcast(this)->getRenderableEntities(); +} + +size_t FilamentAsset::getRenderableEntityCount() const noexcept { + return upcast(this)->getRenderableEntityCount(); +} + const utils::Entity* FilamentAsset::getCameraEntities() const noexcept { return upcast(this)->getCameraEntities(); } diff --git a/libs/viewer/include/viewer/ViewerGui.h b/libs/viewer/include/viewer/ViewerGui.h index 3e3b84364d..8ad7a64c8c 100644 --- a/libs/viewer/include/viewer/ViewerGui.h +++ b/libs/viewer/include/viewer/ViewerGui.h @@ -76,15 +76,25 @@ public: ~ViewerGui(); /** - * Adds the asset's ready-to-render entities into the scene. + * Sets the viewer's current asset. * * The viewer does not claim ownership over the asset or its entities. Clients should use * AssetLoader and ResourceLoader to load an asset before passing it in. * + * This method does not add renderables to the scene; see populateScene(). + * * @param asset The asset to view. * @param instanceToAnimate Optional instance from which to get the animator. */ - void populateScene(FilamentAsset* asset, FilamentInstance* instanceToAnimate = nullptr); + void setAsset(FilamentAsset* asset, FilamentInstance* instanceToAnimate = nullptr); + + /** + * Adds the asset's ready-to-render entities into the scene. + * + * This is used for asychronous loading. It can be called once per frame to gradually add + * entities into the scene as their textures are loaded. + */ + void populateScene(); /** * Removes the current asset from the viewer. diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index 9bc9ac4864..bf2bf618aa 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -394,7 +394,7 @@ ViewerGui::~ViewerGui() { delete mImGuiHelper; } -void ViewerGui::populateScene(FilamentAsset* asset, FilamentInstance* instanceToAnimate) { +void ViewerGui::setAsset(FilamentAsset* asset, FilamentInstance* instanceToAnimate) { if (mAsset != asset) { removeAsset(); mAsset = asset; @@ -407,7 +407,9 @@ void ViewerGui::populateScene(FilamentAsset* asset, FilamentInstance* instanceT updateRootTransform(); mScene->addEntities(asset->getLightEntities(), asset->getLightEntityCount()); } +} +void ViewerGui::populateScene() { auto& tcm = mEngine->getRenderableManager(); static constexpr int kNumAvailable = 128; @@ -847,8 +849,9 @@ void ViewerGui::updateUserInterface() { if (ImGui::CollapsingHeader("Scene")) { ImGui::Indent(); - ImGui::Checkbox("Scale to unit cube", &mSettings.viewer.autoScaleEnabled); - updateRootTransform(); + if (ImGui::Checkbox("Scale to unit cube", &mSettings.viewer.autoScaleEnabled)) { + updateRootTransform(); + } ImGui::Checkbox("Show skybox", &mSettings.viewer.skyboxEnabled); ImGui::ColorEdit3("Background color", &mSettings.viewer.backgroundColor.r); diff --git a/samples/gltf_instances.cpp b/samples/gltf_instances.cpp index 126cc1dd98..c023425e12 100644 --- a/samples/gltf_instances.cpp +++ b/samples/gltf_instances.cpp @@ -254,6 +254,10 @@ int main(int argc, char** argv) { loadAsset(filename); } + FilamentInstance* const instance = app.instanceToAnimate > -1 ? + app.instances[app.instanceToAnimate] : nullptr; + app.viewer->setAsset(app.asset, instance); + arrangeIntoCircle(); loadResources(filename); }; @@ -271,12 +275,8 @@ int main(int argc, char** argv) { auto animate = [&app, arrangeIntoCircle](Engine* engine, View* view, double now) { app.resourceLoader->asyncUpdateLoad(); - FilamentInstance* instance = nullptr; - if (app.instanceToAnimate > -1) { - instance = app.instances[app.instanceToAnimate]; - } app.viewer->updateRootTransform(); - app.viewer->populateScene(app.asset, instance); + app.viewer->populateScene(); app.viewer->applyAnimation(now); // Add a new instance every second until reaching 100 instances. diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index bd3fb9371a..6f71aee402 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -505,6 +505,7 @@ int main(int argc, char** argv) { } else { loadAsset(filename); } + app.viewer->setAsset(app.asset); loadResources(filename); @@ -665,8 +666,8 @@ int main(int argc, char** argv) { // Optionally fit the model into a unit cube at the origin. app.viewer->updateRootTransform(); - // Add renderables to the scene as they become ready. - app.viewer->populateScene(app.asset); + // Gradually add renderables to the scene as their textures become ready. + app.viewer->populateScene(); app.viewer->applyAnimation(now); }; diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index d09b9d285f..346fa02aff 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -702,6 +702,10 @@ Filament.loadClassExtensions = function() { return Filament.vectorToArray(this._getLightEntities()); }; + Filament.gltfio$FilamentAsset.prototype.getRenderableEntities = function() { + return Filament.vectorToArray(this._getRenderableEntities()); + }; + Filament.gltfio$FilamentAsset.prototype.getCameraEntities = function() { return Filament.vectorToArray(this._getCameraEntities()); }; diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 9f003bbbfb..b8a992eddc 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -629,6 +629,7 @@ export class gltfio$FilamentAsset { public getEntityByName(name: string): Entity; public getEntitiesByPrefix(name: string): Entity[]; public getLightEntities(): Entity[]; + public getRenderableEntities(): Entity[]; public getCameraEntities(): Entity[]; public getRoot(): Entity; public popRenderable(): Entity; diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 52c8cb1e85..997b2662a7 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1826,6 +1826,11 @@ class_("gltfio$FilamentAsset") return EntityVector(ptr, ptr + self->getLightEntityCount()); }), allow_raw_pointers()) + .function("_getRenderableEntities", EMBIND_LAMBDA(EntityVector, (FilamentAsset* self), { + const utils::Entity* ptr = self->getRenderableEntities(); + return EntityVector(ptr, ptr + self->getRenderableEntityCount()); + }), allow_raw_pointers()) + .function("_getCameraEntities", EMBIND_LAMBDA(EntityVector, (FilamentAsset* self), { const utils::Entity* ptr = self->getCameraEntities(); return EntityVector(ptr, ptr + self->getCameraEntityCount()); diff --git a/web/samples/helmet.html b/web/samples/helmet.html index ed0d039eab..12e01f2a09 100644 --- a/web/samples/helmet.html +++ b/web/samples/helmet.html @@ -87,15 +87,6 @@ class App { const onDone = () => { this.allowRefresh = true; - // Enable shadows on every renderable. - const entities = asset.getEntities(); - const rm = engine.getRenderableManager(); - for (const entity of entities) { - const instance = rm.getInstance(entity); - rm.setCastShadows(instance, true); - instance.delete(); - } - // Hide the HUD. messages.remove(); }; @@ -149,7 +140,7 @@ class App { tcm.setTransform(inst, this.trackball.getMatrix()); inst.delete(); - // Add renderable entities to the scene as they become ready. + // Gradually add renderables to the scene as their textures become ready. let entity; const popRenderable = () => { entity = this.asset.popRenderable();