diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index f16b76c671..ac04c5326e 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -531,3 +531,12 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz, View *view = (View *) nativeView; return (jint)view->getFogEntity().getId(); } + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz, + jlong nativeView, jlong nativeEngine) { + View *view = (View *) nativeView; + Engine *engine = (Engine *) nativeEngine; + view->clearFrameHistory(*engine); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index 460c25d406..f300d07f88 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -1233,6 +1233,18 @@ public class View { return nGetFogEntity(getNativeObject()); } + /** + * When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view + * keeps a history of previous frame renders associated with the Renderer the view was last + * used with. When switching Renderer, it may be necessary to clear that history by calling + * this method. Similarly, if the whole content of the screen change, like when a cut-scene + * starts, clearing the history might be needed to avoid artifacts due to the previous frame + * being very different. + */ + public void clearFrameHistory(Engine engine) { + nClearFrameHistory(getNativeObject(), engine.getNativeObject()); + } + public long getNativeObject() { if (mNativeObject == 0) { throw new IllegalStateException("Calling method on destroyed View"); @@ -1294,7 +1306,7 @@ public class View { private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w); private static native void nGetMaterialGlobal(long nativeView, int index, float[] out); private static native int nGetFogEntity(long nativeView); - + private static native void nClearFrameHistory(long nativeView, long nativeEngine); /** * List of available ambient occlusion techniques. diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 1a1ed96f51..5264cdae49 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -40,6 +40,7 @@ class CallbackHandler; class Camera; class ColorGrading; +class Engine; class MaterialInstance; class RenderTarget; class Scene; @@ -878,6 +879,17 @@ public: */ utils::Entity getFogEntity() const noexcept; + + /** + * When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view + * keeps a history of previous frame renders associated with the Renderer the view was last + * used with. When switching Renderer, it may be necessary to clear that history by calling + * this method. Similarly, if the whole content of the screen change, like when a cut-scene + * starts, clearing the history might be needed to avoid artifacts due to the previous frame + * being very different. + */ + void clearFrameHistory(Engine& engine) noexcept; + /** * List of available ambient occlusion techniques * @deprecated use AmbientOcclusionOptions::enabled instead diff --git a/filament/src/FrameHistory.h b/filament/src/FrameHistory.h index 905130f5c8..c016ec1bc2 100644 --- a/filament/src/FrameHistory.h +++ b/filament/src/FrameHistory.h @@ -21,6 +21,9 @@ #include #include +#include + +#include namespace filament { diff --git a/filament/src/ResourceAllocator.cpp b/filament/src/ResourceAllocator.cpp index 2990629bce..3f92acf784 100644 --- a/filament/src/ResourceAllocator.cpp +++ b/filament/src/ResourceAllocator.cpp @@ -29,13 +29,14 @@ #include #include -#include #include #include #include #include #include +#include +#include #include #include @@ -89,8 +90,15 @@ void ResourceAllocator::AssociativeContainer::emplace(ARGS&& ... args) } // ------------------------------------------------------------------------------------------------ + ResourceAllocatorInterface::~ResourceAllocatorInterface() = default; +// ------------------------------------------------------------------------------------------------ + +ResourceAllocatorDisposerInterface::~ResourceAllocatorDisposerInterface() = default; + +// ------------------------------------------------------------------------------------------------ + size_t ResourceAllocator::TextureKey::getSize() const noexcept { size_t const pixelCount = width * height * depth; size_t size = pixelCount * FTexture::getFormatSize(format); @@ -110,16 +118,22 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept { ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept : mCacheMaxAge(config.resourceAllocatorCacheMaxAge), - mBackend(driverApi) { + mBackend(driverApi), + mDisposer(std::make_shared(driverApi)) { +} + +ResourceAllocator::ResourceAllocator(std::shared_ptr disposer, + Engine::Config const& config, DriverApi& driverApi) noexcept + : mCacheMaxAge(config.resourceAllocatorCacheMaxAge), + mBackend(driverApi), + mDisposer(std::move(disposer)) { } ResourceAllocator::~ResourceAllocator() noexcept { - assert_invariant(!mTextureCache.size()); - assert_invariant(!mInUseTextures.size()); + assert_invariant(mTextureCache.empty()); } void ResourceAllocator::terminate() noexcept { - assert_invariant(!mInUseTextures.size()); auto& textureCache = mTextureCache; for (auto it = textureCache.begin(); it != textureCache.end();) { mBackend.destroyTexture(it->second.handle); @@ -174,7 +188,7 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name, swizzle[0], swizzle[1], swizzle[2], swizzle[3]); } } - mInUseTextures.emplace(handle, key); + mDisposer->checkout(handle, key); } else { if (swizzle == defaultSwizzle) { handle = mBackend.createTexture( @@ -190,24 +204,21 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name, void ResourceAllocator::destroyTexture(TextureHandle h) noexcept { if constexpr (mEnabled) { - // find the texture in the in-use list (it must be there!) - auto it = mInUseTextures.find(h); - assert_invariant(it != mInUseTextures.end()); - - // move it to the cache - const TextureKey key = it->second; - uint32_t const size = key.getSize(); - - mTextureCache.emplace(key, TextureCachePayload{ h, mAge, size }); - mCacheSize += size; - - // remove it from the in-use list - mInUseTextures.erase(it); + auto const key = mDisposer->checkin(h); + if (UTILS_LIKELY(key.has_value())) { + uint32_t const size = key.value().getSize(); + mTextureCache.emplace(key.value(), TextureCachePayload{ h, mAge, size }); + mCacheSize += size; + } } else { mBackend.destroyTexture(h); } } +ResourceAllocatorDisposerInterface& ResourceAllocator::getDisposer() noexcept { + return *mDisposer; +} + void ResourceAllocator::gc() noexcept { // this is called regularly -- usually once per frame of each Renderer @@ -254,4 +265,46 @@ void ResourceAllocator::purge( mTextureCache.erase(pos); } +// ------------------------------------------------------------------------------------------------ + +ResourceAllocatorDisposer::ResourceAllocatorDisposer(DriverApi& driverApi) noexcept + : mBackend(driverApi) { +} + +ResourceAllocatorDisposer::~ResourceAllocatorDisposer() noexcept { + assert_invariant(mInUseTextures.empty()); +} + +void ResourceAllocatorDisposer::terminate() noexcept { + assert_invariant(mInUseTextures.empty()); +} + +void ResourceAllocatorDisposer::destroy(backend::TextureHandle handle) noexcept { + if (handle) { + auto r = checkin(handle); + if (r.has_value()) { + mBackend.destroyTexture(handle); + } + } +} + +void ResourceAllocatorDisposer::checkout(backend::TextureHandle handle, + ResourceAllocator::TextureKey key) { + mInUseTextures.emplace(handle, key); +} + +std::optional ResourceAllocatorDisposer::checkin( + backend::TextureHandle handle) { + // find the texture in the in-use list (it must be there!) + auto it = mInUseTextures.find(handle); + assert_invariant(it != mInUseTextures.end()); + if (it == mInUseTextures.end()) { + return std::nullopt; + } + TextureKey const key = it->second; + // remove it from the in-use list + mInUseTextures.erase(it); + return key; +} + } // namespace filament diff --git a/filament/src/ResourceAllocator.h b/filament/src/ResourceAllocator.h index 5486592fa5..40f15f3b4a 100644 --- a/filament/src/ResourceAllocator.h +++ b/filament/src/ResourceAllocator.h @@ -28,16 +28,29 @@ #include #include -#include +#include +#include #include +#include +#include #include #include namespace filament { +class ResourceAllocatorDisposer; + // The only reason we use an interface here is for unit-tests, so we can mock this allocator. // This is not too time-critical, so that's okay. + +class ResourceAllocatorDisposerInterface { +public: + virtual void destroy(backend::TextureHandle handle) noexcept = 0; +protected: + virtual ~ResourceAllocatorDisposerInterface(); +}; + class ResourceAllocatorInterface { public: virtual backend::RenderTargetHandle createRenderTarget(const char* name, @@ -60,15 +73,20 @@ public: virtual void destroyTexture(backend::TextureHandle h) noexcept = 0; + virtual ResourceAllocatorDisposerInterface& getDisposer() noexcept = 0; + protected: virtual ~ResourceAllocatorInterface(); }; - class ResourceAllocator final : public ResourceAllocatorInterface { public: + explicit ResourceAllocator(std::shared_ptr disposer, + Engine::Config const& config, backend::DriverApi& driverApi) noexcept; + explicit ResourceAllocator( Engine::Config const& config, backend::DriverApi& driverApi) noexcept; + ~ResourceAllocator() noexcept override; void terminate() noexcept; @@ -93,6 +111,8 @@ public: void destroyTexture(backend::TextureHandle h) noexcept override; + ResourceAllocatorDisposerInterface& getDisposer() noexcept override; + void gc() noexcept; private: @@ -181,6 +201,7 @@ private: using value_type = typename Container::value_type::second_type; size_t size() const { return mContainer.size(); } + bool empty() const { return size() == 0; } iterator begin() { return mContainer.begin(); } const_iterator begin() const { return mContainer.begin(); } iterator end() { return mContainer.end(); } @@ -193,16 +214,35 @@ private: }; using CacheContainer = AssociativeContainer; - using InUseContainer = AssociativeContainer; void purge(ResourceAllocator::CacheContainer::iterator const& pos); backend::DriverApi& mBackend; + std::shared_ptr mDisposer; CacheContainer mTextureCache; - InUseContainer mInUseTextures; size_t mAge = 0; uint32_t mCacheSize = 0; static constexpr bool mEnabled = true; + + friend class ResourceAllocatorDisposer; +}; + +class ResourceAllocatorDisposer final : public ResourceAllocatorDisposerInterface { + using TextureKey = ResourceAllocator::TextureKey; +public: + explicit ResourceAllocatorDisposer(backend::DriverApi& driverApi) noexcept; + ~ResourceAllocatorDisposer() noexcept override; + void terminate() noexcept; + void destroy(backend::TextureHandle handle) noexcept override; + +private: + friend class ResourceAllocator; + void checkout(backend::TextureHandle handle, TextureKey key); + std::optional checkin(backend::TextureHandle handle); + + using InUseContainer = ResourceAllocator::AssociativeContainer; + backend::DriverApi& mBackend; + InUseContainer mInUseTextures; }; } // namespace filament diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 58ac1a6afc..abdd615ab8 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -15,6 +15,8 @@ */ #include "details/View.h" +#include "filament/View.h" + namespace filament { @@ -312,4 +314,8 @@ utils::Entity View::getFogEntity() const noexcept { return downcast(this)->getFogEntity(); } +void View::clearFrameHistory(Engine& engine) noexcept { + downcast(this)->clearFrameHistory(downcast(engine)); +} + } // namespace filament diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 72b5b599c3..8276ebd40b 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -163,8 +163,7 @@ FEngine* FEngine::getEngine(void* token) { FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(instance->mMainThreadId)) << "Engine::createAsync() and Engine::getEngine() must be called on the same thread."; - // we use mResourceAllocator as a proxy for "am I already initialized" - if (!instance->mResourceAllocator) { + if (!instance->mInitialized) { if (UTILS_UNLIKELY(!instance->mDriver)) { // something went horribly wrong during driver initialization instance->mDriverThread.join(); @@ -262,7 +261,7 @@ void FEngine::init() { slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl; - mResourceAllocator = new ResourceAllocator(mConfig, driverApi); + mResourceAllocatorDisposer = std::make_shared(driverApi); mFullScreenTriangleVb = downcast(VertexBuffer::Builder() .vertexCount(3) @@ -425,11 +424,13 @@ void FEngine::init() { } }); }); + + mInitialized = true; } FEngine::~FEngine() noexcept { SYSTRACE_CALL(); - delete mResourceAllocator; + assert_invariant(!mResourceAllocatorDisposer); delete mDriver; if (mOwnPlatform) { PlatformFactory::destroy(&mPlatform); @@ -440,7 +441,7 @@ void FEngine::shutdown() { SYSTRACE_CALL(); // by construction this should never be nullptr - assert_invariant(mResourceAllocator); + assert_invariant(mResourceAllocatorDisposer); FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(mMainThreadId)) << "Engine::shutdown() called from the wrong thread!"; @@ -460,7 +461,8 @@ void FEngine::shutdown() { */ mPostProcessManager.terminate(driver); // free-up post-process manager resources - mResourceAllocator->terminate(); + mResourceAllocatorDisposer->terminate(); + mResourceAllocatorDisposer.reset(); mDFG.terminate(*this); // free-up the DFG mRenderableManager.terminate(); // free-up all renderables mLightManager.terminate(); // free-up all lights diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index b8cdb49877..18abcc2b00 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -86,6 +86,7 @@ namespace filament { class Renderer; class MaterialParser; +class ResourceAllocatorDisposer; namespace backend { class Driver; @@ -254,9 +255,13 @@ public: } } - ResourceAllocator& getResourceAllocator() noexcept { - assert_invariant(mResourceAllocator); - return *mResourceAllocator; + ResourceAllocatorDisposer& getResourceAllocatorDisposer() noexcept { + assert_invariant(mResourceAllocatorDisposer); + return *mResourceAllocatorDisposer; + } + + std::shared_ptr const& getSharedResourceAllocatorDisposer() noexcept { + return mResourceAllocatorDisposer; } void* streamAlloc(size_t size, size_t alignment) noexcept; @@ -490,7 +495,7 @@ private: FTransformManager mTransformManager; FLightManager mLightManager; FCameraManager mCameraManager; - ResourceAllocator* mResourceAllocator = nullptr; + std::shared_ptr mResourceAllocatorDisposer; HwVertexBufferInfoFactory mHwVertexBufferInfoFactory; ResourceList mBufferObjects{ "BufferObject" }; @@ -562,6 +567,8 @@ private: std::thread::id mMainThreadId{}; + bool mInitialized = false; + // Creation parameters Config mConfig; diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 8b537644c0..9fd1758b82 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -60,6 +60,7 @@ #include #include +#include #include #include @@ -88,7 +89,11 @@ FRenderer::FRenderer(FEngine& engine) : mHdrQualityMedium(TextureFormat::R11F_G11F_B10F), mHdrQualityHigh(TextureFormat::RGB16F), mIsRGB8Supported(false), - mUserEpoch(engine.getEngineEpoch()) + mUserEpoch(engine.getEngineEpoch()), + mResourceAllocator(std::make_unique( + engine.getSharedResourceAllocatorDisposer(), + engine.getConfig(), + engine.getDriverApi())) { FDebugRegistry& debugRegistry = engine.getDebugRegistry(); debugRegistry.registerProperty("d.renderer.doFrameCapture", @@ -176,6 +181,7 @@ void FRenderer::terminate(FEngine& engine) { } mFrameInfoManager.terminate(driver); mFrameSkipper.terminate(driver); + mResourceAllocator->terminate(); } void FRenderer::resetUserTime() { @@ -380,7 +386,7 @@ void FRenderer::endFrame() { } // do this before engine.flush() - engine.getResourceAllocator().gc(); + mResourceAllocator->gc(); // Run the component managers' GC in parallel // WARNING: while doing this we can't access any component manager @@ -750,7 +756,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { * Frame graph */ - FrameGraph fg(engine.getResourceAllocator(), + FrameGraph fg(*mResourceAllocator, isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED); auto& blackboard = fg.getBlackboard(); diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index e946e2a0db..e204396434 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,8 @@ namespace filament { +class ResourceAllocator; + namespace backend { class Driver; } // namespace backend @@ -206,6 +209,7 @@ private: tsl::robin_set mPreviousRenderTargets; std::function mBeginFrameInternal; uint64_t mVsyncSteadyClockTimeNano = 0; + std::unique_ptr mResourceAllocator{}; }; FILAMENT_DOWNCAST(Renderer) diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index af0e48ca99..1a3b54deb2 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -17,6 +17,7 @@ #include "details/View.h" #include "Culler.h" +#include "FrameHistory.h" #include "Froxelizer.h" #include "RenderPrimitive.h" #include "ResourceAllocator.h" @@ -118,7 +119,7 @@ void FView::terminate(FEngine& engine) { DriverApi& driver = engine.getDriverApi(); driver.destroyBufferObject(mLightUbh); driver.destroyBufferObject(mRenderableUbh); - drainFrameHistory(engine); + clearFrameHistory(engine); ShadowMapManager::terminate(engine, mShadowMapManager); mPerViewUniforms.terminate(driver); @@ -1007,20 +1008,29 @@ FrameGraphId FView::renderShadowMaps(FEngine& engine, FrameGr void FView::commitFrameHistory(FEngine& engine) noexcept { // Here we need to destroy resources in mFrameHistory.back() + auto& disposer = engine.getResourceAllocatorDisposer(); auto& frameHistory = mFrameHistory; FrameHistoryEntry& last = frameHistory.back(); - last.taa.color.destroy(engine.getResourceAllocator()); - last.ssr.color.destroy(engine.getResourceAllocator()); + disposer.destroy(last.taa.color.handle); + disposer.destroy(last.ssr.color.handle); + last.taa.color.handle.clear(); + last.ssr.color.handle.clear(); // and then push the new history entry to the history stack frameHistory.commit(); } -void FView::drainFrameHistory(FEngine& engine) noexcept { +void FView::clearFrameHistory(FEngine& engine) noexcept { // make sure we free all resources in the history - for (size_t i = 0; i < mFrameHistory.size(); ++i) { - commitFrameHistory(engine); + auto& disposer = engine.getResourceAllocatorDisposer(); + auto& frameHistory = mFrameHistory; + for (size_t i = 0; i < frameHistory.size(); ++i) { + FrameHistoryEntry& last = frameHistory[i]; + disposer.destroy(last.taa.color.handle); + disposer.destroy(last.ssr.color.handle); + last.taa.color.handle.clear(); + last.ssr.color.handle.clear(); } } diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 0624af1ce6..73e7c9dc61 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -422,6 +422,10 @@ public: // (e.g.: after the FrameGraph execution). void commitFrameHistory(FEngine& engine) noexcept; + // Clean-up the whole history, free all resources. This is typically called when the View is + // being terminated. Or we're changing Renderer. + void clearFrameHistory(FEngine& engine) noexcept; + // create the picking query View::PickingQuery& pick(uint32_t x, uint32_t y, backend::CallbackHandler* handler, View::PickingQueryResultCallback callback) noexcept; @@ -485,10 +489,6 @@ private: Culler::result_type* visibleMask, size_t count); - // Clean-up the whole history, free all resources. This is typically called when the View is - // being terminated. - void drainFrameHistory(FEngine& engine) noexcept; - // we don't inline this one, because the function is quite large and there is not much to // gain from inlining. static FScene::RenderableSoa::iterator partition( diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index e52cdef5c4..fec8580326 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -24,6 +24,7 @@ #include "FrameGraphPass.h" #include "FrameGraphRenderPass.h" #include "FrameGraphTexture.h" +#include "ResourceAllocator.h" #include "details/Engine.h" diff --git a/filament/test/filament_framegraph_test.cpp b/filament/test/filament_framegraph_test.cpp index 019bcb6776..684f3e2bf2 100644 --- a/filament/test/filament_framegraph_test.cpp +++ b/filament/test/filament_framegraph_test.cpp @@ -34,6 +34,10 @@ using namespace backend; class MockResourceAllocator : public ResourceAllocatorInterface { uint32_t handle = 0; + struct MockDisposer : public ResourceAllocatorDisposerInterface { + void destroy(backend::TextureHandle) noexcept override {} + } disposer; + public: backend::RenderTargetHandle createRenderTarget(const char* name, backend::TargetBufferFlags targetBufferFlags, @@ -60,6 +64,10 @@ public: void destroyTexture(backend::TextureHandle h) noexcept override { } + + ResourceAllocatorDisposerInterface& getDisposer() noexcept override { + return disposer; + } }; class FrameGraphTest : public testing::Test { diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index e1bc5c3755..e5ec3fad9b 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -683,7 +683,8 @@ class_("View") .function("isStencilBufferEnabled", &View::isStencilBufferEnabled) .function("setMaterialGlobal", &View::setMaterialGlobal) .function("getMaterialGlobal", &View::getMaterialGlobal) - .function("getFogEntity", &View::getFogEntity); + .function("getFogEntity", &View::getFogEntity) + .function("clearFrameHistory", &View::clearFrameHistory); /// Scene ::core class:: Flat container of renderables and lights. /// See also the [Engine] methods `createScene` and `destroyScene`.