diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index fd76718f38..6abd774aac 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -8,6 +8,7 @@ A new header is inserted each time a *tag* is created.
- The Android support libraries (gltfio and filament-utils) now use dynamic linking.
- Screen-space refraction is now supported.
- Removed depth-prepass related APIs.
+- gltfio: add asynchronous API to ResourceLoader.
## v1.4.5
diff --git a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt
index f44ba072d5..032697f5c7 100644
--- a/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt
+++ b/android/filament-utils-android/src/main/java/com/google/android/filament/utils/ModelViewer.kt
@@ -57,6 +57,10 @@ class ModelViewer {
var animator: Animator? = null
private set
+ @Suppress("unused")
+ val progress
+ get() = resourceLoader.asyncGetLoadProgress()
+
val engine: Engine
val scene: Scene
val view: View
@@ -69,6 +73,7 @@ class ModelViewer {
private val renderer: Renderer
private var swapChain: SwapChain? = null
private var assetLoader: AssetLoader
+ private var resourceLoader: ResourceLoader
private val eyePos = DoubleArray(3)
private val target = DoubleArray(3)
@@ -91,6 +96,7 @@ class ModelViewer {
view.camera = camera
assetLoader = AssetLoader(engine, MaterialProvider(engine), EntityManager.get())
+ resourceLoader = ResourceLoader(engine)
// Always add a direct light source since it is required for shadowing.
// We highly recommend adding an indirect light as well.
@@ -140,9 +146,7 @@ class ModelViewer {
destroyModel()
asset = assetLoader.createAssetFromJson(buffer)
asset?.let { asset ->
- val resourceLoader = ResourceLoader(engine)
- resourceLoader.loadResources(asset)
- resourceLoader.destroy()
+ resourceLoader.asyncBeginLoad(asset)
animator = asset.animator
asset.releaseSourceData()
scene.addEntities(asset.entities)
@@ -156,12 +160,10 @@ class ModelViewer {
destroyModel()
asset = assetLoader.createAssetFromJson(buffer)
asset?.let { asset ->
- val resourceLoader = ResourceLoader(engine)
for (uri in asset.resourceUris) {
resourceLoader.addResourceData(uri, callback(uri))
}
- resourceLoader.loadResources(asset)
- resourceLoader.destroy()
+ resourceLoader.asyncBeginLoad(asset)
animator = asset.animator
asset.releaseSourceData()
scene.addEntities(asset.entities)
@@ -203,12 +205,17 @@ class ModelViewer {
return
}
+ // Allow the resource loader to finalize textures that have become ready.
+ resourceLoader.asyncUpdateLoad()
+
+ // Extract the camera basis from the helper and push it to the Filament camera.
cameraManipulator.getLookAt(eyePos, target, upward)
camera.lookAt(
eyePos[0], eyePos[1], eyePos[2],
target[0], target[1], target[2],
upward[0], upward[1], upward[2])
+ // Render the scene, unless the renderer wants to skip the frame.
if (renderer.beginFrame(swapChain!!)) {
renderer.render(view)
renderer.endFrame()
@@ -223,6 +230,7 @@ class ModelViewer {
destroyModel()
assetLoader.destroy()
+ resourceLoader.destroy()
engine.destroyEntity(light)
engine.destroyRenderer(renderer)
diff --git a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp
index 1dad00871a..c4e5f9ba5d 100644
--- a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp
+++ b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp
@@ -76,3 +76,25 @@ Java_com_google_android_filament_gltfio_ResourceLoader_nLoadResources(JNIEnv*, j
FilamentAsset* asset = (FilamentAsset*) nativeAsset;
loader->loadResources(asset);
}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_com_google_android_filament_gltfio_ResourceLoader_nAsyncBeginLoad(JNIEnv*, jclass,
+ jlong nativeLoader, jlong nativeAsset) {
+ ResourceLoader* loader = (ResourceLoader*) nativeLoader;
+ FilamentAsset* asset = (FilamentAsset*) nativeAsset;
+ return loader->asyncBeginLoad(asset);
+}
+
+extern "C" JNIEXPORT jfloat JNICALL
+Java_com_google_android_filament_gltfio_ResourceLoader_nAsyncGetLoadProgress(JNIEnv*, jclass,
+ jlong nativeLoader) {
+ ResourceLoader* loader = (ResourceLoader*) nativeLoader;
+ return loader->asyncGetLoadProgress();
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_android_filament_gltfio_ResourceLoader_nAsyncUpdateLoad(JNIEnv*, jclass,
+ jlong nativeLoader) {
+ ResourceLoader* loader = (ResourceLoader*) nativeLoader;
+ loader->asyncUpdateLoad();
+}
diff --git a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java
index 12b92f91be..10820ba6ef 100644
--- a/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java
+++ b/android/gltfio-android/src/main/java/com/google/android/filament/gltfio/ResourceLoader.java
@@ -26,9 +26,10 @@ import java.lang.reflect.Method;
import java.nio.Buffer;
/**
- * Uploads vertex buffers and textures to the GPU and computes tangents.
+ * Prepares and uploads vertex buffers and textures to the GPU.
*
- *
For a usage example, see the documentation for {@link AssetLoader}.
+ * For a usage example, see the documentation for {@link AssetLoader}.
+ * All methods should be called from the main thread.
*
* @see AssetLoader
* @see FilamentAsset
@@ -58,12 +59,15 @@ public class ResourceLoader {
/**
* Feeds the binary content of an external resource into the loader's URI cache.
*
- * ResourceLoader does not know how to download external resources on its own
- * (for example, external resources might come from a filesystem, a database, or the internet)
- * so this method allows clients to download external resources and push them to the loader.
+ * On some platforms, `ResourceLoader` does not know how to download external resources on its
+ * own (external resources might come from a filesystem, a database, or the internet) so this
+ * method allows clients to download external resources and push them to the loader.
*
- * When loading GLB files (as opposed to JSON-based glTF files), clients typically do not
- * need to call this method.
+ * Every resource should be passed in before calling [loadResources] or [asyncBeginLoad]. See
+ * also [FilamentAsset#getResourceUris].
+ *
+ * When loading GLB files (as opposed to JSON-based glTF files), clients typically do not
+ * need to call this method.
*
* @param uri the string path that matches an image URI or buffer URI in the glTF
* @param buffer the binary blob corresponding to the given URI
@@ -76,7 +80,7 @@ public class ResourceLoader {
}
/**
- * Checks if the given resource has already been loaded.
+ * Checks if the given resource has already been added to the URI cache.
*/
public boolean hasResourceData(@NonNull String uri) {
return nHasResourceData(mNativeObject, uri);
@@ -86,8 +90,7 @@ public class ResourceLoader {
* Iterates through all external buffers and images and creates corresponding Filament objects
* (vertex buffers, textures, etc), which become owned by the asset.
*
- * This is the main entry point for ResourceLoader, and only needs to be called
- * once.
+ * NOTE: this is a synchronous API, please see [asyncBeginLoad] as an alternative.
*
* @param asset the Filament asset that contains URI-based resources
* @return self (for daisy chaining)
@@ -98,10 +101,43 @@ public class ResourceLoader {
return this;
}
+ /**
+ * Starts an asynchronous resource load.
+ *
+ * Returns false if the loading process was unable to start.
+ *
+ * This is an alternative to #loadResources and requires periodic calls to #asyncUpdateLoad.
+ * On multi-threaded systems this creates threads for texture decoding.
+ */
+ public boolean asyncBeginLoad(@NonNull FilamentAsset asset) {
+ return nAsyncBeginLoad(mNativeObject, asset.getNativeObject());
+ }
+
+ /**
+ * Gets the status of an asynchronous resource load as a percentage in [0,1].
+ */
+ public float asyncGetLoadProgress() {
+ return nAsyncGetLoadProgress(mNativeObject);
+ }
+
+ /**
+ * Updates an asynchronous load by performing any pending work that must take place
+ * on the main thread.
+ *
+ * Clients must periodically call this until #asyncGetLoadProgress returns 100%.
+ * After progress reaches 100%, calling this is harmless; it just does nothing.
+ */
+ public void asyncUpdateLoad() {
+ nAsyncUpdateLoad(mNativeObject);
+ }
+
private static native long nCreateResourceLoader(long nativeEngine);
private static native void nDestroyResourceLoader(long nativeLoader);
private static native void nAddResourceData(long nativeLoader, String url, Buffer buffer,
int remaining);
private static native boolean nHasResourceData(long nativeLoader, String url);
private static native void nLoadResources(long nativeLoader, long nativeAsset);
+ private static native boolean nAsyncBeginLoad(long nativeLoader, long nativeAsset);
+ private static native float nAsyncGetLoadProgress(long nativeLoader);
+ private static native void nAsyncUpdateLoad(long nativeLoader);
}
diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h
index 65131e2f04..1b8beef109 100644
--- a/libs/gltfio/include/gltfio/ResourceLoader.h
+++ b/libs/gltfio/include/gltfio/ResourceLoader.h
@@ -58,20 +58,15 @@ struct ResourceConfiguration {
/**
* \class ResourceLoader ResourceLoader.h gltfio/ResourceLoader.h
- * \brief Asynchronously uploads vertex buffers and textures to the GPU and computes tangents.
+ * \brief Prepares and uploads vertex buffers and textures to the GPU.
*
* For a usage example, see the documentation for AssetLoader.
*
- * In theory, this class could cache a map of URL's to data blobs and could therefore be useful
- * across multiple assets. However, clients should feel free to immediately destroy this after
- * calling loadResources. There is no need to wait for resources to finish uploading because this is
- * done in the the background.
- *
* ResourceLoader must be destroyed on the same thread that calls filament::Renderer::render()
* because it listens to filament::backend::BufferDescriptor callbacks in order to determine when to
* free CPU-side data blobs.
*
- * \todo If clients persist their ResourceLoader, Texture objects are currently re-created upon
+ * \todo If clients persist their ResourceLoader, Filament textures are currently re-created upon
* subsequent re-loads of the same asset. To fix this, we would need to enable shared ownership
* of Texture objects between ResourceLoader and FilamentAsset.
*/
@@ -83,9 +78,24 @@ public:
~ResourceLoader();
/**
- * Adds raw resource data into a cache for platforms that do not have filesystem access.
+ * Feeds the binary content of an external resource into the loader's URI cache.
+ *
+ * On some platforms, `ResourceLoader` does not know how to download external resources on its
+ * own (external resources might come from a filesystem, a database, or the internet) so this
+ * method allows clients to download external resources and push them to the loader.
+ *
+ * Every resource should be passed in before calling #loadResources or #asyncBeginLoad. See
+ * also FilamentAsset#getResourceUris.
+ *
+ * When loading GLB files (as opposed to JSON-based glTF files), clients typically do not
+ * need to call this method.
*/
- void addResourceData(const char* url, BufferDescriptor&& buffer);
+ void addResourceData(const char* uri, BufferDescriptor&& buffer);
+
+ /**
+ * Checks if the given resource has already been added to the URI cache.
+ */
+ bool hasResourceData(const char* uri) const;
/**
* Loads resources for the given asset from the filesystem or data cache and "finalizes" the
@@ -96,13 +106,33 @@ public:
* be loaded.
*
* Note: this method is synchronous and blocks until all textures have been decoded.
+ * For an asynchronous alternative, see #asyncBeginLoad.
*/
bool loadResources(FilamentAsset* asset);
/**
- * Checks if the given resource has already been loaded.
+ * Starts an asynchronous resource load.
+ *
+ * Returns false if the loading process was unable to start.
+ *
+ * This is an alternative to #loadResources and requires periodic calls to #asyncUpdateLoad.
+ * On multi-threaded systems this creates threads for texture decoding.
*/
- bool hasResourceData(const char* url) const;
+ bool asyncBeginLoad(FilamentAsset* asset);
+
+ /**
+ * Gets the status of an asynchronous resource load as a percentage in [0,1].
+ */
+ float asyncGetLoadProgress() const;
+
+ /**
+ * Updates an asynchronous load by performing any pending work that must take place
+ * on the main thread.
+ *
+ * Clients must periodically call this until #asyncGetLoadProgress returns 100%.
+ * After progress reaches 100%, calling this is harmless; it just does nothing.
+ */
+ void asyncUpdateLoad();
private:
bool loadResources(details::FFilamentAsset* asset, bool async);
diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp
index a370e86b50..4fd34da184 100644
--- a/libs/gltfio/src/ResourceLoader.cpp
+++ b/libs/gltfio/src/ResourceLoader.cpp
@@ -28,15 +28,15 @@
#include
-#include