diff --git a/README.md b/README.md index 5cd0c006c5..625dd40c3a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.56.6' + implementation 'com.google.android.filament:filament-android:1.56.7' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.56.6' +pod 'Filament', '~> 1.56.7' ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ffccaa339a..09643b5d20 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.56.8 + + ## v1.56.7 diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 56aecc66dd..d6e941209b 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -392,11 +392,11 @@ Java_com_google_android_filament_Engine_nIsValidSwapChain(JNIEnv*, jclass, return (jboolean)engine->isValid((SwapChain*)nativeSwapChain); } -extern "C" JNIEXPORT void JNICALL +extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass, - jlong nativeEngine) { + jlong nativeEngine, jlong timeout) { Engine* engine = (Engine*) nativeEngine; - engine->flushAndWait(); + return engine->flushAndWait((uint64_t)timeout); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 1e49bf54a3..e73e82944c 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -1271,7 +1271,28 @@ public class Engine { * {@link android.view.SurfaceHolder.Callback#surfaceDestroyed surfaceDestroyed}.

*/ public void flushAndWait() { - nFlushAndWait(getNativeObject()); + flushAndWait(Fence.WAIT_FOR_EVER); + } + + /** + * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until + * all commands to this point are executed. Note that does guarantee that the + * hardware is actually finished. + * + * A timeout can be specified, if for some reason this flushAndWait doesn't complete before the timeout, it will + * return false, true otherwise. + * + *

This is typically used right after destroying the SwapChain, + * in cases where a guarantee about the SwapChain destruction is needed in a + * timely fashion, such as when responding to Android's + * android.view.SurfaceHolder.Callback.surfaceDestroyed

+ * + * @param timeout A timeout in nanoseconds + * @return true if successful, false if flushAndWait timed out, in which case it wasn't successful and commands + * might still be executing on both the CPU and GPU sides. + */ + public boolean flushAndWait(long timeout) { + return nFlushAndWait(getNativeObject(), timeout); } /** @@ -1437,7 +1458,7 @@ public class Engine { private static native boolean nIsValidRenderTarget(long nativeEngine, long nativeTarget); private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain); private static native void nDestroyEntity(long nativeEngine, int entity); - private static native void nFlushAndWait(long nativeEngine); + private static native boolean nFlushAndWait(long nativeEngine, long timeout); private static native void nFlush(long nativeEngine); private static native boolean nIsPaused(long nativeEngine); private static native void nSetPaused(long nativeEngine, boolean paused); 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 0bcd1299b7..bcf6f5378f 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 @@ -1420,10 +1420,10 @@ public class View { /** * Upscaling quality * LOW: bilinear filtered blit. Fastest, poor quality - * MEDIUM: AMD FidelityFX FSR1 w/ mobile optimizations + * MEDIUM: Qualcomm Snapdragon Game Super Resolution (SGSR) 1.0 * HIGH: AMD FidelityFX FSR1 w/ mobile optimizations * ULTRA: AMD FidelityFX FSR1 - * FSR1 require a well anti-aliased (MSAA or TAA), noise free scene. + * FSR1 and SGSR require a well anti-aliased (MSAA or TAA), noise free scene. Avoid FXAA and dithering. * * The default upscaling quality is set to LOW. */ diff --git a/android/gradle.properties b/android/gradle.properties index e4f778ef79..d1baf95447 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.56.6 +VERSION_NAME=1.56.7 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index d218a39170..9f0364f94e 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -252,6 +252,7 @@ set(MATERIAL_SRCS src/materials/fsr/fsr_easu_mobile.mat src/materials/fsr/fsr_easu_mobileF.mat src/materials/fsr/fsr_rcas.mat + src/materials/sgsr/sgsr1.mat src/materials/resolveDepth.mat src/materials/separableGaussianBlur.mat src/materials/skybox.mat @@ -504,6 +505,12 @@ add_custom_command( APPEND ) +add_custom_command( + OUTPUT "${MATERIAL_DIR}/sgsr1.filamat" + DEPENDS src/materials/sgsr/sgsr1_shader_mobile.fs + APPEND +) + add_custom_command( OUTPUT "${MATERIAL_DIR}/separableGaussianBlur.filamat" DEPENDS src/materials/separableGaussianBlur.vs diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 8fb44af5dc..5092e16c38 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -970,6 +970,25 @@ public: */ void flushAndWait(); + /** + * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until + * all commands to this point are executed. Note that does guarantee that the + * hardware is actually finished. + * + * A timeout can be specified, if for some reason this flushAndWait doesn't complete before the timeout, it will + * return false, true otherwise. + * + *

This is typically used right after destroying the SwapChain, + * in cases where a guarantee about the SwapChain destruction is needed in a + * timely fashion, such as when responding to Android's + * android.view.SurfaceHolder.Callback.surfaceDestroyed

+ * + * @param timeout A timeout in nanoseconds + * @return true if successful, false if flushAndWait timed out, in which case it wasn't successful and commands + * might still be executing on both the CPU and GPU sides. + */ + bool flushAndWait(uint64_t timeout); + /** * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait * for commands to be either executed or the hardware finished. diff --git a/filament/include/filament/Options.h b/filament/include/filament/Options.h index 93bba80cd8..dc06143dd4 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -86,10 +86,10 @@ struct DynamicResolutionOptions { /** * Upscaling quality * LOW: bilinear filtered blit. Fastest, poor quality - * MEDIUM: AMD FidelityFX FSR1 w/ mobile optimizations + * MEDIUM: Qualcomm Snapdragon Game Super Resolution (SGSR) 1.0 * HIGH: AMD FidelityFX FSR1 w/ mobile optimizations * ULTRA: AMD FidelityFX FSR1 - * FSR1 require a well anti-aliased (MSAA or TAA), noise free scene. + * FSR1 and SGSR require a well anti-aliased (MSAA or TAA), noise free scene. Avoid FXAA and dithering. * * The default upscaling quality is set to LOW. */ diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 5f1af80aaf..455067739c 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -338,6 +338,10 @@ void Engine::flushAndWait() { downcast(this)->flushAndWait(); } +bool Engine::flushAndWait(uint64_t timeout) { + return downcast(this)->flushAndWait(timeout); +} + void Engine::flush() { downcast(this)->flush(); } diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index b7a51aae0b..d9ab8b0c16 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -311,6 +311,7 @@ static const PostProcessManager::StaticMaterialInfo sMaterialList[] = { { "fsr_easu_mobile", MATERIAL(FSR_EASU_MOBILE) }, { "fsr_easu_mobileF", MATERIAL(FSR_EASU_MOBILEF) }, { "fsr_rcas", MATERIAL(FSR_RCAS) }, + { "sgsr1", MATERIAL(SGSR1) }, { "debugShadowCascades", MATERIAL(DEBUGSHADOWCASCADES) }, { "resolveDepth", MATERIAL(RESOLVEDEPTH) }, { "shadowmap", MATERIAL(SHADOWMAP) }, @@ -2328,7 +2329,7 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver, mi->setParameter("vignette", vignetteParameters); mi->setParameter("vignetteColor", vignetteOptions.color); mi->setParameter("dithering", colorGradingConfig.dithering); - mi->setParameter("fxaa", colorGradingConfig.fxaa); + mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance); mi->setParameter("temporalNoise", temporalNoise); mi->commit(driver); @@ -2544,7 +2545,7 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, mi->setParameter("bloom", bloomParameters); mi->setParameter("vignette", vignetteParameters); mi->setParameter("vignetteColor", vignetteOptions.color); - mi->setParameter("fxaa", colorGradingConfig.fxaa); + mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance); mi->setParameter("temporalNoise", temporalNoise); mi->setParameter("viewport", float4{ float(vp.left) / input.width, @@ -2562,7 +2563,7 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, FrameGraphId PostProcessManager::fxaa(FrameGraph& fg, FrameGraphId const input, filament::Viewport const& vp, - TextureFormat const outFormat, bool const translucent) noexcept { + TextureFormat const outFormat, bool const preserveAlphaChannel) noexcept { struct PostProcessFXAA { FrameGraphId input; @@ -2587,7 +2588,7 @@ FrameGraphId PostProcessManager::fxaa(FrameGraph& fg, auto const& material = getPostProcessMaterial("fxaa"); - PostProcessVariant const variant = translucent ? + PostProcessVariant const variant = preserveAlphaChannel ? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE; FMaterialInstance* const mi = @@ -2920,20 +2921,171 @@ FrameGraphId PostProcessManager::rcas( } FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool translucent, - DynamicResolutionOptions dsrOptions, FrameGraphId const input, - filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, - SamplerMagFilter filter) noexcept { - + bool sourceHasLuminance, DynamicResolutionOptions dsrOptions, + FrameGraphId const input, filament::Viewport const& vp, + FrameGraphTexture::Descriptor const& outDesc, SamplerMagFilter filter) noexcept { // The code below cannot handle sub-resources assert_invariant(fg.getSubResourceDescriptor(input).layer == 0); assert_invariant(fg.getSubResourceDescriptor(input).level == 0); - const bool lowQualityFallback = translucent && dsrOptions.quality != QualityLevel::LOW; + const bool lowQualityFallback = translucent; if (lowQualityFallback) { - // FidelityFX-FSR doesn't support the alpha channel currently + // FidelityFX-FSR nor SGSR support the source alpha channel currently dsrOptions.quality = QualityLevel::LOW; } + if (dsrOptions.quality == QualityLevel::LOW) { + return upscaleBilinear(fg, translucent, dsrOptions, input, vp, outDesc, filter); + } + if (dsrOptions.quality == QualityLevel::MEDIUM) { + return upscaleSGSR1(fg, sourceHasLuminance, dsrOptions, input, vp, outDesc); + } + return upscaleFSR1(fg, dsrOptions, input, vp, outDesc); +} + +FrameGraphId PostProcessManager::upscaleBilinear(FrameGraph& fg, bool translucent, + DynamicResolutionOptions dsrOptions, FrameGraphId const input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, + SamplerMagFilter filter) noexcept { + + struct QuadBlitData { + FrameGraphId input; + FrameGraphId output; + }; + + auto const& ppQuadBlit = fg.addPass(dsrOptions.enabled ? "upscaling" : "compositing", + [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture("upscaled output", outDesc); + data.output = builder.write(data.output, FrameGraphTexture::Usage::COLOR_ATTACHMENT); + builder.declareRenderPass(builder.getName(data.output), { + .attachments = { .color = { data.output } }, + .clearFlags = TargetBufferFlags::DEPTH }); + }, + [this, vp, translucent, filter](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { + bindPostProcessDescriptorSet(driver); + + // helper to enable blending + auto enableTranslucentBlending = [](PipelineState& pipeline) { + pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; + pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; + pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA; + pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA; + }; + + auto color = resources.getTexture(data.input); + auto const& inputDesc = resources.getDescriptor(data.input); + + // -------------------------------------------------------------------------------- + // set uniforms + + auto& material = getPostProcessMaterial("blitLow"); + FMaterialInstance* const mi = + PostProcessMaterial::getMaterialInstance(mEngine, material); + mi->setParameter("color", color, { + .filterMag = filter + }); + + mi->setParameter("levelOfDetail", 0.0f); + + mi->setParameter("viewport", float4{ + float(vp.left) / inputDesc.width, + float(vp.bottom) / inputDesc.height, + float(vp.width) / inputDesc.width, + float(vp.height) / inputDesc.height + }); + mi->commit(driver); + mi->use(driver); + + auto out = resources.getRenderPassInfo(); + + auto pipeline = getPipelineState(material.getMaterial(mEngine)); + if (translucent) { + enableTranslucentBlending(pipeline); + } + renderFullScreenQuad(out, pipeline, driver); + }); + + auto output = ppQuadBlit->output; + + // if we had to take the low quality fallback, we still do the "sharpen pass" + if (dsrOptions.sharpness > 0.0f) { + output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent); + } + + // we rely on automatic culling of unused render passes + return output; +} + +FrameGraphId PostProcessManager::upscaleSGSR1(FrameGraph& fg, bool sourceHasLuminance, + DynamicResolutionOptions dsrOptions, FrameGraphId const input, + filament::Viewport const&, FrameGraphTexture::Descriptor const& outDesc) noexcept { + + struct QuadBlitData { + FrameGraphId input; + FrameGraphId output; + }; + + auto const& ppQuadBlit = fg.addPass(dsrOptions.enabled ? "upscaling" : "compositing", + [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture("upscaled output", outDesc); + data.output = builder.write(data.output, FrameGraphTexture::Usage::COLOR_ATTACHMENT); + builder.declareRenderPass(builder.getName(data.output), { + .attachments = { .color = { data.output } }, + .clearFlags = TargetBufferFlags::DEPTH }); + }, + [this, sourceHasLuminance](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { + bindPostProcessDescriptorSet(driver); + + auto color = resources.getTexture(data.input); + auto const& inputDesc = resources.getDescriptor(data.input); + + // -------------------------------------------------------------------------------- + // set uniforms + + auto const& material = getPostProcessMaterial("sgsr1"); + + PostProcessVariant const variant = sourceHasLuminance ? + PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE; + + FMaterialInstance* const mi = + PostProcessMaterial::getMaterialInstance(mEngine, material, variant); + + mi->setParameter("color", color, { + // The SGSR documentation doesn't clarify if LINEAR or NEAREST should be used. The + // sample code uses NEAREST, but that doesn't seem right, since it would mean the + // LERP mode would not be a LERP, and the non-edges would be sampled as NEAREST. + .filterMag = SamplerMagFilter::LINEAR + }); + + mi->setParameter("viewport", float4{ + 1.0f / inputDesc.width, + 1.0f / inputDesc.height, + float(inputDesc.width), + float(inputDesc.height), + }); + + mi->commit(driver); + mi->use(driver); + + auto out = resources.getRenderPassInfo(); + commitAndRenderFullScreenQuad(driver, out, mi, variant); + + }); + + auto output = ppQuadBlit->output; + + // we rely on automatic culling of unused render passes + return output; +} + +FrameGraphId PostProcessManager::upscaleFSR1(FrameGraph& fg, + DynamicResolutionOptions dsrOptions, FrameGraphId const input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc) noexcept { + const bool twoPassesEASU = mWorkaroundSplitEasu && (dsrOptions.quality == QualityLevel::MEDIUM || dsrOptions.quality == QualityLevel::HIGH); @@ -2964,7 +3116,7 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool .attachments = { .color = { data.output }, .depth = { data.depth }}, .clearFlags = TargetBufferFlags::DEPTH }); }, - [this, twoPassesEASU, dsrOptions, vp, translucent, filter](FrameGraphResources const& resources, + [this, twoPassesEASU, dsrOptions, vp](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { bindPostProcessDescriptorSet(driver); @@ -2989,14 +3141,6 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool float2{ inputDesc.width, inputDesc.height }); }; - // helper to enable blending - auto enableTranslucentBlending = [](PipelineState& pipeline) { - pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; - pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE; - pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA; - pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA; - }; - auto color = resources.getTexture(data.input); auto const& inputDesc = resources.getDescriptor(data.input); auto const& outputDesc = resources.getDescriptor(data.output); @@ -3023,29 +3167,21 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool } { // just a scope to not leak local variables - const std::string_view blitterNames[4] = { - "blitLow", "fsr_easu_mobile", "fsr_easu_mobile", "fsr_easu" }; - unsigned const index = std::min(3u, unsigned(dsrOptions.quality)); + const std::string_view blitterNames[2] = { "fsr_easu_mobile", "fsr_easu" }; + unsigned const index = std::min(1u, unsigned(dsrOptions.quality) - 2); easuMaterial = &getPostProcessMaterial(blitterNames[index]); FMaterialInstance* const mi = PostProcessMaterial::getMaterialInstance(mEngine, *easuMaterial); - if (dsrOptions.quality != QualityLevel::LOW) { - setEasuUniforms(mi, inputDesc, outputDesc); - } + + setEasuUniforms(mi, inputDesc, outputDesc); + mi->setParameter("color", color, { - .filterMag = (dsrOptions.quality == QualityLevel::LOW) ? - filter : SamplerMagFilter::LINEAR + .filterMag = SamplerMagFilter::LINEAR }); - if (blitterNames[index] != "blitLow") { - mi->setParameter("resolution", - float4{outputDesc.width, outputDesc.height, 1.0f / outputDesc.width, - 1.0f / outputDesc.height}); - } - - if (blitterNames[index] == "blitLow") { - mi->setParameter("levelOfDetail", 0.0f); - } + mi->setParameter("resolution", + float4{outputDesc.width, outputDesc.height, 1.0f / outputDesc.width, + 1.0f / outputDesc.height}); mi->setParameter("viewport", float4{ float(vp.left) / inputDesc.width, @@ -3066,29 +3202,19 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool auto pipeline0 = getPipelineState(splitEasuMaterial->getMaterial(mEngine)); auto pipeline1 = getPipelineState(easuMaterial->getMaterial(mEngine)); pipeline1.rasterState.depthFunc = SamplerCompareFunc::NE; - if (translucent) { - enableTranslucentBlending(pipeline0); - enableTranslucentBlending(pipeline1); - } driver.beginRenderPass(out.target, out.params); driver.draw(pipeline0, mFullScreenQuadRph, 0, 3, 1); driver.draw(pipeline1, mFullScreenQuadRph, 0, 3, 1); driver.endRenderPass(); } else { auto pipeline = getPipelineState(easuMaterial->getMaterial(mEngine)); - if (translucent) { - enableTranslucentBlending(pipeline); - } renderFullScreenQuad(out, pipeline, driver); } }); auto output = ppQuadBlit->output; - - // if we had to take the low quality fallback, we still do the "sharpen pass" - if (dsrOptions.sharpness > 0.0f && - (dsrOptions.quality != QualityLevel::LOW || lowQualityFallback)) { - output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent); + if (dsrOptions.sharpness > 0.0f) { + output = rcas(fg, dsrOptions.sharpness, output, outDesc, false); } // we rely on automatic culling of unused render passes diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index c435cdccd9..677cec30cb 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -89,7 +89,7 @@ public: bool asSubpass{}; bool customResolve{}; bool translucent{}; - bool fxaa{}; + bool outputLuminance{}; // Whether to output luminance in the alpha channel. Ignored by the TRANSLUCENT variant. bool dithering{}; backend::TextureFormat ldrFormat{}; }; @@ -227,7 +227,7 @@ public: // Anti-aliasing FrameGraphId fxaa(FrameGraph& fg, FrameGraphId input, Viewport const& vp, - backend::TextureFormat outFormat, bool translucent) noexcept; + backend::TextureFormat outFormat, bool preserveAlphaChannel) noexcept; // Temporal Anti-aliasing void TaaJitterCamera( @@ -251,12 +251,25 @@ public: // high quality upscaler // - when translucent, reverts to LINEAR - // - doens't handle sub-resouces + // - doesn't handle sub-resouces FrameGraphId upscale(FrameGraph& fg, bool translucent, + bool sourceHasLuminance, DynamicResolutionOptions dsrOptions, + FrameGraphId input, Viewport const& vp, + FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept; + + FrameGraphId upscaleBilinear(FrameGraph& fg, bool translucent, DynamicResolutionOptions dsrOptions, FrameGraphId input, Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept; + FrameGraphId upscaleFSR1(FrameGraph& fg, + DynamicResolutionOptions dsrOptions, FrameGraphId input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc) noexcept; + + FrameGraphId upscaleSGSR1(FrameGraph& fg, bool sourceHasLuminance, + DynamicResolutionOptions dsrOptions, FrameGraphId input, + filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc) noexcept; + FrameGraphId rcas( FrameGraph& fg, float sharpness, diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index a6dc21cab3..ba606c6eb4 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -230,7 +230,7 @@ private: // Inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly. // Because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations. // Each ShadowMap is currently 88 bytes (total of ~12KB for 128 shadow maps) - using ShadowMapStorage = std::aligned_storage::type; + using ShadowMapStorage = std::aligned_storage_t; using ShadowMapCacheContainer = std::array; ShadowMapCacheContainer mShadowMapCache; uint32_t mDirectionalShadowMapCount = 0; diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 1cc9a951be..2116fae4c1 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -665,50 +665,28 @@ void FEngine::flush() { } void FEngine::flushAndWait() { - FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isPaused()) - << "Cannot call flushAndWait() when rendering thread is paused!"; + flushAndWait(FENCE_WAIT_FOR_EVER); +} -#if defined(__ANDROID__) +bool FEngine::flushAndWait(uint64_t timeout) { + FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isPaused()) + << "Cannot call Engine::flushAndWait() when rendering thread is paused!"; // first make sure we've not terminated filament FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isExitRequested()) - << "calling Engine::flushAndWait() after Engine::shutdown()!"; - -#endif + << "Calling Engine::flushAndWait() after Engine::shutdown()!"; // enqueue finish command -- this will stall in the driver until the GPU is done getDriverApi().finish(); -#if defined(__ANDROID__) && !defined(NDEBUG) - - // then create a fence that will trigger when we're past the finish() above - size_t tryCount = 8; - FFence* fence = FEngine::createFence(); - UTILS_NOUNROLL - do { - FenceStatus status = fence->wait(FFence::Mode::FLUSH,250000000u); - // if the fence didn't trigger after 250ms, check that the command queue thread is still - // running (otherwise indicating a precondition violation). - if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) { - FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isExitRequested()) - << "called Engine::shutdown() WHILE in Engine::flushAndWait()!"; - tryCount--; - FILAMENT_CHECK_POSTCONDITION(tryCount) << "flushAndWait() failed inexplicably after 2s"; - // if the thread is still running, maybe we just need to give it more time - continue; - } - break; - } while (true); + FFence* fence = createFence(); + FenceStatus const status = fence->wait(FFence::Mode::FLUSH, timeout); destroy(fence); -#else - - FFence::waitAndDestroy(createFence(), FFence::Mode::FLUSH); - -#endif - // finally, execute callbacks that might have been scheduled getDriver().purge(); + + return status == FenceStatus::CONDITION_SATISFIED; } // ----------------------------------------------------------------------------------------------- diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index da83a726a6..28376a5b6b 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -394,6 +394,7 @@ public: void setPaused(bool paused); void flushAndWait(); + bool flushAndWait(uint64_t timeout); // flush the current buffer void flush(); @@ -716,7 +717,7 @@ public: &features.backend.opengl.assert_native_window_is_valid, true }, { "engine.shadows.use_shadow_atlas", "Uses an array of atlases to store shadow maps.", - &features.engine.shadows.use_shadow_atlas, true }, + &features.engine.shadows.use_shadow_atlas, false }, { "features.engine.debug.assert_material_instance_in_use", "Assert when a MaterialInstance is destroyed while it is in use by RenderableManager.", &features.engine.debug.assert_material_instance_in_use, false } diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 6057c92a52..186c6249c7 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -681,6 +681,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { hasColorGrading && !bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled; + // whether we're scaled at all + bool scaled = any(notEqual(scale, float2(1.0f))); + // asSubpass is disabled with TAA (although it's supported) because performance was degraded // on qualcomm hardware -- we might need a backend dependent toggle at some point const PostProcessManager::ColorGradingConfig colorGradingConfig{ @@ -695,18 +698,15 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { hasColorGrading && !engine.debug.renderer.disable_subpasses, .translucent = needsAlphaChannel, - .fxaa = hasFXAA, + .outputLuminance = hasFXAA || scaled, // ignored by translucent variants (false) .dithering = hasDithering, - .ldrFormat = (hasColorGrading && hasFXAA) ? + .ldrFormat = (hasColorGrading && (hasFXAA || scaled)) ? TextureFormat::RGBA8 : getLdrFormat(needsAlphaChannel) }; // by construction (msaaSampleCount) both asSubpass and customResolve can't be true assert_invariant(colorGradingConfig.asSubpass + colorGradingConfig.customResolve < 2); - // whether we're scaled at all - bool scaled = any(notEqual(scale, float2(1.0f))); - // vp is the user defined viewport within the View filament::Viewport const& vp = view.getViewport(); @@ -1351,8 +1351,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { } if (hasFXAA) { - input = ppm.fxaa(fg, input, xvp, colorGradingConfig.ldrFormat, - !hasColorGrading || needsAlphaChannel); + bool const preserveAlphaChannel = needsAlphaChannel || + (hasColorGrading && colorGradingConfig.outputLuminance); + input = ppm.fxaa(fg, input, xvp, colorGradingConfig.ldrFormat, preserveAlphaChannel); // the padded buffer is resolved now xvp.left = xvp.bottom = 0; svp = xvp; @@ -1360,9 +1361,11 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { if (scaled) { mightNeedFinalBlit = false; auto viewport = DEBUG_DYNAMIC_SCALING ? xvp : vp; - input = ppm.upscale(fg, needsAlphaChannel, dsrOptions, input, xvp, { - .width = viewport.width, .height = viewport.height, - .format = colorGradingConfig.ldrFormat }, SamplerMagFilter::LINEAR); + bool const sourceHasLuminance = !needsAlphaChannel && + (hasColorGrading && colorGradingConfig.outputLuminance); + input = ppm.upscale(fg, needsAlphaChannel, sourceHasLuminance, dsrOptions, input, xvp, { + .width = viewport.width, .height = viewport.height, + .format = colorGradingConfig.ldrFormat }, SamplerMagFilter::LINEAR); xvp.left = xvp.bottom = 0; svp = xvp; } diff --git a/filament/src/materials/colorGrading/colorGrading.mat b/filament/src/materials/colorGrading/colorGrading.mat index 7667b42d9b..83712beafe 100644 --- a/filament/src/materials/colorGrading/colorGrading.mat +++ b/filament/src/materials/colorGrading/colorGrading.mat @@ -41,7 +41,7 @@ material { }, { type : int, - name : fxaa + name : outputLuminance }, { type : float, @@ -166,10 +166,10 @@ void postProcess(inout PostProcessInputs postProcess) { color = dither(color, materialParams.temporalNoise); } - // kill alpha computations when opaque / fxaa luminance + // kill alpha computations when opaque / luminance #if POST_PROCESS_OPAQUE color.a = 1.0; - if (materialParams.fxaa > 0) { + if (materialParams.outputLuminance > 0) { color.a = luminance(color.rgb); } #endif diff --git a/filament/src/materials/sgsr/LICENSE.txt b/filament/src/materials/sgsr/LICENSE.txt new file mode 100644 index 0000000000..d9d513c758 --- /dev/null +++ b/filament/src/materials/sgsr/LICENSE.txt @@ -0,0 +1,29 @@ +Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +SPDX-License-Identifier: BSD-3-Clause \ No newline at end of file diff --git a/filament/src/materials/sgsr/sgsr1.mat b/filament/src/materials/sgsr/sgsr1.mat new file mode 100644 index 0000000000..38fe23fc02 --- /dev/null +++ b/filament/src/materials/sgsr/sgsr1.mat @@ -0,0 +1,80 @@ +material { + name : sgsr1, + parameters : [ + { + type : sampler2d, + name : color, + precision: medium + }, + { + type : float4, + name : viewport, + precision: high + } + ], + variables : [ + vertex + ], + depthWrite : false, + depthCulling : false, + domain: postprocess +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.normalizedUV = uvToRenderTargetUV(postProcess.normalizedUV); + postProcess.vertex.xy = postProcess.normalizedUV; + } +} + +fragment { + + void dummy(){} + + precision mediump float; + precision highp int; + +#if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) + #define gather textureGather +#else + vec4 gather(const mediump sampler2D color, highp vec2 p, const int comp) { + highp ivec2 i = ivec2(p * materialParams.viewport.zw - 0.5); + vec4 d; + d[0] = texelFetchOffset(color, i, 0, ivec2(0, 1))[comp]; + d[1] = texelFetchOffset(color, i, 0, ivec2(1, 1))[comp]; + d[2] = texelFetchOffset(color, i, 0, ivec2(1, 0))[comp]; + d[3] = texelFetchOffset(color, i, 0, ivec2(0, 0))[comp]; + return d; + } +#endif + +/* +* Operation modes: +* RGBA -> 1 +* RGBY -> 3 +* LERP -> 4 +*/ + +// SGSR cannot support translucency; +// So we hijack the POST_PROCESS variant to select whether a luminance channel is present +#if POST_PROCESS_OPAQUE +# define OperationMode 1 +#else +# define OperationMode 3 +#endif + +/* +* If set, will use edge direction to improve visual quality +* Expect a minimal cost increase +*/ +#define UseEdgeDirection +#define EdgeThreshold 8.0 / 255.0 +#define EdgeSharpness 2.0 + +#include "sgsr1_shader_mobile.fs" + + void postProcess(inout PostProcessInputs postProcess) { + highp vec4 vp[1] = vec4[1]( materialParams.viewport ); + postProcess.color = sgsr(materialParams_color, variable_vertex.xy, vp); + } +} diff --git a/filament/src/materials/sgsr/sgsr1_shader_mobile.fs b/filament/src/materials/sgsr/sgsr1_shader_mobile.fs new file mode 100644 index 0000000000..edc937f4ad --- /dev/null +++ b/filament/src/materials/sgsr/sgsr1_shader_mobile.fs @@ -0,0 +1,132 @@ +//============================================================================================================ +// +// +// Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause +// +//============================================================================================================ + +//////////////////////// +// USER CONFIGURATION // +//////////////////////// + +float fastLanczos2(float x) +{ + float wA = x-4.0; + float wB = x*wA-wA; + wA *= wA; + return wB*wA; +} + +#if defined(UseEdgeDirection) +vec2 weightY(float dx, float dy, float c, vec3 data) +#else +vec2 weightY(float dx, float dy, float c, float data) +#endif +{ +#if defined(UseEdgeDirection) + float std = data.x; + vec2 dir = data.yz; + + float edgeDis = ((dx*dir.y)+(dy*dir.x)); + float x = (((dx*dx)+(dy*dy))+((edgeDis*edgeDis)*((clamp(((c*c)*std),0.0,1.0)*0.7)+-1.0))); +#else + float std = data; + float x = ((dx*dx)+(dy*dy))* 0.55 + clamp(abs(c)*std, 0.0, 1.0); +#endif + + float w = fastLanczos2(x); + return vec2(w, w * c); +} + +vec2 edgeDirection(vec4 left, vec4 right) +{ + vec2 dir; + float RxLz = (right.x + (-left.z)); + float RwLy = (right.w + (-left.y)); + vec2 delta; + delta.x = (RxLz + RwLy); + delta.y = (RxLz + (-RwLy)); + float lengthInv = inversesqrt((delta.x * delta.x+ 3.075740e-05) + (delta.y * delta.y)); + dir.x = (delta.x * lengthInv); + dir.y = (delta.y * lengthInv); + return dir; +} + +vec4 sgsr(mediump sampler2D ps0, highp vec2 in_TEXCOORD0, highp vec4 ViewportInfo[1]) +{ + vec4 color; + if(OperationMode == 1) + color.xyz = textureLod(ps0,in_TEXCOORD0.xy,0.0).xyz; + else + color.xyzw = textureLod(ps0,in_TEXCOORD0.xy,0.0).xyzw; + +#if OperationMode != 4 + if (OperationMode!=4) + { + highp vec2 imgCoord = ((in_TEXCOORD0.xy*ViewportInfo[0].zw)+vec2(-0.5,0.5)); + highp vec2 imgCoordPixel = floor(imgCoord); + highp vec2 coord = (imgCoordPixel*ViewportInfo[0].xy); + vec2 pl = (imgCoord+(-imgCoordPixel)); + vec4 left = gather(ps0,coord, OperationMode); + + float edgeVote = abs(left.z - left.y) + abs(color[OperationMode] - left.y) + abs(color[OperationMode] - left.z) ; + if(edgeVote > EdgeThreshold) + { + coord.x += ViewportInfo[0].x; + + vec4 right = gather(ps0,coord + vec2(ViewportInfo[0].x, 0.0), OperationMode); + vec4 upDown; + upDown.xy = gather(ps0,coord + vec2(0.0, -ViewportInfo[0].y),OperationMode).wz; + upDown.zw = gather(ps0,coord+ vec2(0.0, ViewportInfo[0].y), OperationMode).yx; + + float mean = (left.y+left.z+right.x+right.w)*0.25; + left = left - vec4(mean); + right = right - vec4(mean); + upDown = upDown - vec4(mean); + color.w = color[OperationMode] - mean; + + float sum = (((((abs(left.x)+abs(left.y))+abs(left.z))+abs(left.w)) + + (((abs(right.x)+abs(right.y))+abs(right.z))+abs(right.w))) + + (((abs(upDown.x)+abs(upDown.y))+abs(upDown.z))+abs(upDown.w))); + +#if defined(UseEdgeDirection) + float sumMean = 1.014185e+01 / sum; + float std = (sumMean * sumMean); + vec3 data = vec3(std, edgeDirection(left, right)); +#else + // taken from sgsr sample + float data = 2.181818 / sum; +#endif + + vec2 aWY = weightY(pl.x, pl.y+1.0, upDown.x,data); + aWY += weightY(pl.x-1.0, pl.y+1.0, upDown.y,data); + aWY += weightY(pl.x-1.0, pl.y-2.0, upDown.z,data); + aWY += weightY(pl.x, pl.y-2.0, upDown.w,data); + aWY += weightY(pl.x+1.0, pl.y-1.0, left.x,data); + aWY += weightY(pl.x, pl.y-1.0, left.y,data); + aWY += weightY(pl.x, pl.y, left.z,data); + aWY += weightY(pl.x+1.0, pl.y, left.w,data); + aWY += weightY(pl.x-1.0, pl.y-1.0, right.x,data); + aWY += weightY(pl.x-2.0, pl.y-1.0, right.y,data); + aWY += weightY(pl.x-2.0, pl.y, right.z,data); + aWY += weightY(pl.x-1.0, pl.y, right.w,data); + + float finalY = aWY.y/aWY.x; + float maxY = max(max(left.y,left.z),max(right.x,right.w)); + float minY = min(min(left.y,left.z),min(right.x,right.w)); + float deltaY = clamp(EdgeSharpness*finalY, minY, maxY) -color.w; + + //smooth high contrast input + deltaY = clamp(deltaY, -23.0 / 255.0, 23.0 / 255.0); + + color.x = clamp(color.x+deltaY, 0.0, 1.0); + color.y = clamp(color.y+deltaY, 0.0, 1.0); + color.z = clamp(color.z+deltaY, 0.0, 1.0); + } + } +#endif + + color.w = 1.0; //assume alpha channel is not used + return color; +} \ No newline at end of file diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 1133525c5a..e5e319aab0 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.56.6" + spec.version = "1.56.7" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.56.6/filament-v1.56.6-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.56.7/filament-v1.56.7-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/libs/imageio/include/imageio/ImageDecoder.h b/libs/imageio/include/imageio/ImageDecoder.h index cd341c49d8..4573e0b2c8 100644 --- a/libs/imageio/include/imageio/ImageDecoder.h +++ b/libs/imageio/include/imageio/ImageDecoder.h @@ -33,7 +33,7 @@ public: SRGB }; - // Returns linear floating-point data, or a non-valid image if an error occured. + // Returns linear floating-point data, or a non-valid image if an error occurred. static LinearImage decode(std::istream& stream, const std::string& sourceName, ColorSpace sourceSpace = ColorSpace::SRGB); diff --git a/libs/matdbg/src/SourceFormatter.cpp b/libs/matdbg/src/SourceFormatter.cpp index c068a34f4b..fe7c3f88ed 100644 --- a/libs/matdbg/src/SourceFormatter.cpp +++ b/libs/matdbg/src/SourceFormatter.cpp @@ -25,14 +25,32 @@ namespace filament::matdbg { #if defined(__linux__) || defined(__APPLE__) std::string SourceFormatter::format(char const* source) { - std::string const TMP_FILENAME = "/tmp/matdbg-tmp-src.cpp"; + char output[1024]; - std::string original(source); - FILE* fp; - fp = fopen(TMP_FILENAME.c_str(), "w"); - if (!fp) { - return original; + // First check if clang-format is available. + FILE* fp = popen("clang-format -version", "r"); + + // Need to drain the output + while (fgets(output, 1024, fp) != NULL) {} + + int status = pclose(fp); + if (!fp || !WEXITSTATUS(status)) { + std::call_once(mClangWarningFlag, []() { + utils::slog.w << "[matdbg] unable to run clang-format to format shader file. " + << "Please make sure it's installed." << utils::io::endl; + }); + return source; } + + // Write the source to a temp file for formatting + char const* TMP_FILENAME = "/tmp/matdbg-tmp-src.cpp"; + fp = fopen(TMP_FILENAME, "w"); + if (!fp) { + return source; + } + + // Unknown if the input is 0-terminated so we put it in a string first. + std::string original(source); fputs(original.c_str(), fp); fflush(fp); pclose(fp); @@ -45,22 +63,16 @@ std::string SourceFormatter::format(char const* source) { "}'"; fp = popen(("clang-format " + CLANG_FORMAT_OPTIONS + "< " + TMP_FILENAME).c_str(), "r"); - if (!fp) { - std::call_once(mClangWarningFlag, []() { - utils::slog.w << "[matdbg] unable to run clang-format to format shader file. " - << "Please make sure it's installed."; - }); return original; } - char output[1024]; std::stringstream outStream; while (fgets(output, 1024, fp) != NULL) { outStream << output; } - int status = pclose(fp); + status = pclose(fp); if (WEXITSTATUS(status)) { utils::slog.w << "[matdbg] clang-format failed with code=" << WEXITSTATUS(status) << utils::io::endl; @@ -69,11 +81,11 @@ std::string SourceFormatter::format(char const* source) { } #else std::string SourceFormatter::format(char const* source) { - std::call_once(mClangWarningFlag, []() { + std::call_once(mClangWarningFlag, []() { utils::slog.w <<"[matdbg]: source formatting is not available on this platform" << utils::io::endl; }); - return ""; + return source; } #endif diff --git a/libs/utils/src/Panic.cpp b/libs/utils/src/Panic.cpp index 1b8921ac06..e0aec064fa 100644 --- a/libs/utils/src/Panic.cpp +++ b/libs/utils/src/Panic.cpp @@ -43,7 +43,7 @@ class UserPanicHandler { struct CallBack { Panic::PanicHandlerCallback handler = nullptr; void* user = nullptr; - void call(Panic const& panic) const noexcept { + void call(Panic const& panic) const { if (UTILS_UNLIKELY(handler)) { handler(user, panic); } @@ -64,7 +64,7 @@ public: return data; } - void call(Panic const& panic) const noexcept { + void call(Panic const& panic) const { getCallback().call(panic); } diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 3a8340976c..36a97d6c37 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.56.6", + "version": "1.56.7", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js",