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 076bd6357f..a82d6dc4ec 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 @@ -220,6 +220,7 @@ class ModelViewer(val engine: Engine) : android.view.View.OnTouchListener { */ fun destroyModel() { fetchResourcesJob?.cancel() + resourceLoader.asyncCancelLoad() asset?.let { asset -> this.scene.removeEntities(asset.entities) assetLoader.destroyAsset(asset) diff --git a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp index 8f4162f83d..0f84627103 100644 --- a/android/gltfio-android/src/main/cpp/ResourceLoader.cpp +++ b/android/gltfio-android/src/main/cpp/ResourceLoader.cpp @@ -99,3 +99,10 @@ Java_com_google_android_filament_gltfio_ResourceLoader_nAsyncUpdateLoad(JNIEnv*, ResourceLoader* loader = (ResourceLoader*) nativeLoader; loader->asyncUpdateLoad(); } + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_gltfio_ResourceLoader_nAsyncCancelLoad(JNIEnv*, jclass, + jlong nativeLoader) { + ResourceLoader* loader = (ResourceLoader*) nativeLoader; + loader->asyncCancelLoad(); +} 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 9d235a8542..d3c2ab2fae 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 @@ -146,6 +146,16 @@ public class ResourceLoader { nAsyncUpdateLoad(mNativeObject); } + /** + * Cancels pending decoder jobs and frees all CPU-side texel data. + * + * Calling this is only necessary if the asyncBeginLoad API was used + * and cancellation is required before progress reaches 100%. + */ + public void asyncCancelLoad() { + nAsyncCancelLoad(mNativeObject); + } + private static native long nCreateResourceLoader(long nativeEngine, boolean normalizeSkinningWeights, boolean recomputeBoundingBoxes); private static native void nDestroyResourceLoader(long nativeLoader); @@ -156,4 +166,5 @@ public class ResourceLoader { private static native boolean nAsyncBeginLoad(long nativeLoader, long nativeAsset); private static native float nAsyncGetLoadProgress(long nativeLoader); private static native void nAsyncUpdateLoad(long nativeLoader); + private static native void nAsyncCancelLoad(long nativeLoader); } diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index a82e8d62d0..bb36a3a7d8 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -130,6 +130,14 @@ public: */ void asyncUpdateLoad(); + /** + * Cancels pending decoder jobs and frees all CPU-side texel data. + * + * Calling this is only necessary if the asyncBeginLoad API was used + * and cancellation is required before progress reaches 100%. + */ + void asyncCancelLoad(); + private: bool loadResources(FFilamentAsset* asset, bool async); void applySparseData(FFilamentAsset* asset) const; diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 1a8342b400..4a54a6e9aa 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -104,10 +104,12 @@ struct ResourceLoader::Impl { void computeTangents(FFilamentAsset* asset); bool createTextures(bool async); + void cancelTextureDecoding(); void addTextureCacheEntry(const TextureSlot& tb); void bindTextureToMaterial(const TextureSlot& tb); void decodeSingleTexture(); void uploadPendingTextures(); + void releasePendingTextures(); ~Impl(); }; @@ -450,6 +452,10 @@ bool ResourceLoader::asyncBeginLoad(FilamentAsset* asset) { return loadResources(upcast(asset), true); } +void ResourceLoader::asyncCancelLoad() { + pImpl->cancelTextureDecoding(); +} + float ResourceLoader::asyncGetLoadProgress() const { const float finished = pImpl->mNumDecoderTasksFinished; const float total = pImpl->mNumDecoderTasks; @@ -527,6 +533,20 @@ void ResourceLoader::Impl::uploadPendingTextures() { for (auto& pair : mUriTextureCache) upload(pair.second.get(), *mEngine); } +void ResourceLoader::Impl::releasePendingTextures() { + auto release = [this](TextureCacheEntry* entry, Engine& engine) { + Texture* texture = entry->texture; + uint8_t* texels = entry->texels; + if (texture && texels && !entry->completed) { + // Normally the ownership of these texels is transferred to PixelBufferDescriptor, but + // if uploads have been cancelled then we need to free them explicitly. + free(texels); + } + }; + for (auto& pair : mBufferTextureCache) release(pair.second.get(), *mEngine); + for (auto& pair : mUriTextureCache) release(pair.second.get(), *mEngine); +} + void ResourceLoader::Impl::addTextureCacheEntry(const TextureSlot& tb) { TextureCacheEntry* entry = nullptr; @@ -602,6 +622,19 @@ void ResourceLoader::Impl::bindTextureToMaterial(const TextureSlot& tb) { asset->bindTexture(tb, entry->texture); } } +void ResourceLoader::Impl::cancelTextureDecoding() { + JobSystem* js = &mEngine->getJobSystem(); + if (mDecoderRootJob) { + js->waitAndRelease(mDecoderRootJob); + mDecoderRootJob = nullptr; + } + releasePendingTextures(); + mBufferTextureCache.clear(); + mUriTextureCache.clear(); + mCurrentAsset = nullptr; + mNumDecoderTasksFinished = 0; + mNumDecoderTasks = 0; +} bool ResourceLoader::Impl::createTextures(bool async) { // If any decoding jobs are still underway, wait for them to finish.