From f6339fb66ac53d745c4d0cb5f3cfcdcc38cc5fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 18 Jun 2024 18:57:47 +0200 Subject: [PATCH 1/9] update filament to latest commit --- filament | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament b/filament index 3ca9b9cd..cab799f5 160000 --- a/filament +++ b/filament @@ -1 +1 @@ -Subproject commit 3ca9b9cd0fdb6435af3a084813edd6ead8abc2b8 +Subproject commit cab799f531ce2fe871083b201efa2c142ed0bbb5 From 1f59f23f671bb75c1a26ff82fd52584a90af84e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 18 Jun 2024 18:58:20 +0200 Subject: [PATCH 2/9] remove filament_material_instance_getter.patch as PR was merged https://github.com/google/filament/pull/7686 --- .../filament_material_instance_getter.patch | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 package/filament_material_instance_getter.patch diff --git a/package/filament_material_instance_getter.patch b/package/filament_material_instance_getter.patch deleted file mode 100644 index 54125503..00000000 --- a/package/filament_material_instance_getter.patch +++ /dev/null @@ -1,93 +0,0 @@ -diff --git a/filament/include/filament/MaterialInstance.h b/filament/include/filament/MaterialInstance.h -index a0edd1355..7252f9a4a 100644 ---- a/filament/include/filament/MaterialInstance.h -+++ b/filament/include/filament/MaterialInstance.h -@@ -234,6 +234,28 @@ public: - setParameter(name, strlen(name), type, color); - } - -+ /** -+ * Gets the value of a parameter by name. -+ * -+ * @param name Name of the parameter as defined by Material. Cannot be nullptr. -+ * @param nameLength Length in `char` of the name parameter. -+ * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. -+ */ -+ template -+ T getParameter(const char* UTILS_NONNULL name, size_t nameLength) const; -+ -+ /** inline helper to provide the name as a null-terminated C string */ -+ template> -+ inline T getParameter(StringLiteral name) const { -+ return getParameter(name.data, name.size); -+ } -+ -+ /** inline helper to provide the name as a null-terminated C string */ -+ template> -+ inline T getParameter(const char* UTILS_NONNULL name) const { -+ return getParameter(name, strlen(name)); -+ } -+ - /** - * Set-up a custom scissor rectangle; by default it is disabled. - * -diff --git a/filament/src/MaterialInstance.cpp b/filament/src/MaterialInstance.cpp -index 33f3ab764..ee0af2de1 100644 ---- a/filament/src/MaterialInstance.cpp -+++ b/filament/src/MaterialInstance.cpp -@@ -75,6 +75,12 @@ inline void FMaterialInstance::setParameterImpl(std::string_view name, - setParameterUntypedImpl(name, value, count); - } - -+template -+T FMaterialInstance::getParameterImpl(std::string_view name) const noexcept { -+ ssize_t offset = mMaterial->getUniformInterfaceBlock().getFieldOffset(name, 0); -+ return downcast(this)->getUniformBuffer().getUniform(offset); -+} -+ - // ------------------------------------------------------------------------------------------------ - - template -@@ -183,6 +189,28 @@ template UTILS_PUBLIC void MaterialInstance::setParameter (const char* - - // ------------------------------------------------------------------------------------------------ - -+template -+T MaterialInstance::getParameter(const char* name, size_t nameLength) const { -+ return downcast(this)->getParameterImpl({ name, nameLength }); -+} -+ -+// explicit template instantiation of our supported types -+template UTILS_PUBLIC float MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC int32_t MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC uint32_t MaterialInstance::getParameter(const char* name, size_t nameLength) const; -+template UTILS_PUBLIC int2 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC int3 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC int4 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC uint2 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC uint3 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC uint4 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC float2 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC float3 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC float4 MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+template UTILS_PUBLIC mat3f MaterialInstance::getParameter (const char* name, size_t nameLength) const; -+ -+// ------------------------------------------------------------------------------------------------ -+ - Material const* MaterialInstance::getMaterial() const noexcept { - return downcast(this)->getMaterial(); - } -diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h -index 6be23b7e0..2f05bcf3f 100644 ---- a/filament/src/details/MaterialInstance.h -+++ b/filament/src/details/MaterialInstance.h -@@ -231,6 +231,9 @@ private: - void setParameterImpl(std::string_view name, - FTexture const* texture, TextureSampler const& sampler); - -+ template -+ T getParameterImpl(std::string_view name) const noexcept; -+ - FMaterialInstance() noexcept; - void initDefaultInstance(FEngine& engine, FMaterial const* material); - From cb7c1fa85eb971e6b74daaf09d21997473d470ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 18 Jun 2024 19:02:43 +0200 Subject: [PATCH 3/9] update ios patch --- package/filament_ios_simulator.patch | 83 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/package/filament_ios_simulator.patch b/package/filament_ios_simulator.patch index f7eef22e..6d648759 100644 --- a/package/filament_ios_simulator.patch +++ b/package/filament_ios_simulator.patch @@ -1,8 +1,8 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index ed32949873a..35c10e6717e 100644 +index 283778b32..4d4ddfc4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -611,7 +611,11 @@ endif() +@@ -609,7 +609,11 @@ endif() string(TOLOWER "${DIST_ARCH}" DIST_ARCH) string(REPLACE "amd64" "x86_64" DIST_ARCH "${DIST_ARCH}") if (NOT DIST_DIR) @@ -16,7 +16,7 @@ index ed32949873a..35c10e6717e 100644 # ================================================================================================== diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md -index 4a1a9c7fa7e..aa2ee9c76e0 100644 +index 4a1a9c7fa..aa2ee9c76 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,3 +7,4 @@ for next branch cut* header. @@ -25,10 +25,10 @@ index 4a1a9c7fa7e..aa2ee9c76e0 100644 ## Release notes for next branch cut +- ios: Add support for Apple Silicon (arm64) simulator diff --git a/build.sh b/build.sh -index 56562cc1c6c..3cb660aa1f9 100755 +index ab6440f45..c817f5a8d 100755 --- a/build.sh +++ b/build.sh -@@ -581,9 +581,9 @@ function build_ios_target { +@@ -593,9 +593,9 @@ function build_ios_target { local platform=$3 echo "Building iOS ${lc_target} (${arch}) for ${platform}..." @@ -40,9 +40,9 @@ index 56562cc1c6c..3cb660aa1f9 100755 if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then cmake \ -@@ -598,14 +598,14 @@ function build_ios_target { - ${MATDBG_OPTION} \ +@@ -611,14 +611,14 @@ function build_ios_target { ${MATOPT_OPTION} \ + ${STEREOSCOPIC_OPTION} \ ../.. - ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \ + ln -sf "out/cmake-ios-${lc_target}-${arch}-${platform}/compile_commands.json" \ @@ -57,7 +57,7 @@ index 56562cc1c6c..3cb660aa1f9 100755 ${BUILD_COMMAND} ${INSTALL_COMMAND} fi -@@ -640,16 +640,26 @@ function build_ios { +@@ -653,16 +653,26 @@ function build_ios { if [[ "${ISSUE_DEBUG_BUILD}" == "true" ]]; then build_ios_target "Debug" "arm64" "iphoneos" if [[ "${IOS_BUILD_SIMULATOR}" == "true" ]]; then @@ -90,7 +90,7 @@ index 56562cc1c6c..3cb660aa1f9 100755 fi archive_ios "Debug" -@@ -657,19 +667,39 @@ function build_ios { +@@ -670,19 +680,39 @@ function build_ios { if [[ "${ISSUE_RELEASE_BUILD}" == "true" ]]; then build_ios_target "Release" "arm64" "iphoneos" @@ -141,7 +141,7 @@ index 56562cc1c6c..3cb660aa1f9 100755 } diff --git a/build/ios/create-xc-frameworks.sh b/build/ios/create-xc-frameworks.sh new file mode 100755 -index 00000000000..cf720cb7e4e +index 000000000..cf720cb7e --- /dev/null +++ b/build/ios/create-xc-frameworks.sh @@ -0,0 +1,107 @@ @@ -253,12 +253,12 @@ index 00000000000..cf720cb7e4e + eval $CMD +done diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec -index 3ed06a44db5..3ffe1632473 100644 +index 45fce3980..b9674ca59 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -8,12 +8,6 @@ Pod::Spec.new do |spec| spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.3/filament-v1.51.3-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.0/filament-v1.53.0-ios.tgz" } - # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. - spec.pod_target_xcconfig = { @@ -402,7 +402,7 @@ index 3ed06a44db5..3ffe1632473 100644 ss.dependency "Filament/filamat" ss.dependency "Filament/tsl" diff --git a/ios/samples/README.md b/ios/samples/README.md -index 4c132849ee4..6c9c165eabb 100644 +index 4c132849e..6c9c165ea 100644 --- a/ios/samples/README.md +++ b/ios/samples/README.md @@ -47,7 +47,7 @@ build Filament in Release mode, replace `debug` with `release` in the above `bui @@ -415,7 +415,7 @@ index 4c132849ee4..6c9c165eabb 100644 ``` $ ./build.sh -s -p ios -i debug diff --git a/ios/samples/app-template.yml b/ios/samples/app-template.yml -index e8e14c50715..9c407448d16 100644 +index e8e14c507..9c407448d 100644 --- a/ios/samples/app-template.yml +++ b/ios/samples/app-template.yml @@ -17,25 +17,27 @@ targetTemplates: @@ -457,7 +457,7 @@ index e8e14c50715..9c407448d16 100644 GCC_PREPROCESSOR_DEFINITIONS: ["FILAMENT_APP_USE_METAL=1", "$(inherited)"] opengl: diff --git a/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj b/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj -index b47f4c00e36..ea4c11fe046 100644 +index b47f4c00e..ea4c11fe0 100644 --- a/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj +++ b/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj @@ -3,34 +3,112 @@ @@ -774,7 +774,7 @@ index b47f4c00e36..ea4c11fe046 100644 PRODUCT_BUNDLE_IDENTIFIER = "${SAMPLE_CODE_DISAMBIGUATOR}.google.filament.backend-test"; SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; diff --git a/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme b/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme -index f4026437bcd..d6462758330 100644 +index f4026437b..d64627583 100644 --- a/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme +++ b/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme @@ -1,6 +1,6 @@ @@ -786,7 +786,7 @@ index f4026437bcd..d6462758330 100644 Date: Tue, 18 Jun 2024 19:26:13 +0200 Subject: [PATCH 4/9] update bullet3 --- bullet3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bullet3 b/bullet3 index 6bb8d112..e9c461b0 160000 --- a/bullet3 +++ b/bullet3 @@ -1 +1 @@ -Subproject commit 6bb8d1123d8a55d407b19fd3357c724d0f5c9d3c +Subproject commit e9c461b0ace140d5c73972760781d94b7b5eee53 From af263d1d3474a1064e04da2c81de7df0ba28588b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 18 Jun 2024 19:33:13 +0200 Subject: [PATCH 5/9] header updates after updating filament --- .../filament/include/backend/DriverEnums.h | 26 ++- .../libs/filament/include/backend/Handle.h | 15 ++ .../libs/filament/include/backend/Platform.h | 31 +++ .../include/backend/PresentCallable.h | 4 +- .../libs/filament/include/backend/Program.h | 24 ++ .../include/backend/TargetBufferInfo.h | 27 ++- .../backend/platforms/OpenGLPlatform.h | 23 ++ .../backend/platforms/PlatformEGLAndroid.h | 21 ++ .../backend/platforms/VulkanPlatform.h | 49 +++- .../include/filamat/MaterialBuilder.h | 13 ++ .../libs/filament/include/filament/Engine.h | 111 +++++++-- .../include/filament/MaterialChunkType.h | 1 + .../filament/include/filament/MaterialEnums.h | 2 +- .../include/filament/MaterialInstance.h | 5 + .../include/filament/RenderableManager.h | 78 ++++--- .../filament/include/filament/SwapChain.h | 42 ++-- .../libs/filament/include/filament/View.h | 7 + .../filament/include/gltfio/AssetLoader.h | 21 ++ .../include/utils/FixedCapacityVector.h | 16 +- .../libs/filament/include/utils/Panic.h | 211 ++++++++++++++---- .../include/utils/StructureOfArrays.h | 4 +- .../libs/filament/include/utils/algorithm.h | 82 ++++++- .../libs/filament/include/utils/bitset.h | 12 +- .../libs/filament/include/utils/sstream.h | 36 +++ .../filament/include/backend/DriverEnums.h | 26 ++- .../libs/filament/include/backend/Handle.h | 15 ++ .../libs/filament/include/backend/Platform.h | 31 +++ .../include/backend/PresentCallable.h | 4 +- .../libs/filament/include/backend/Program.h | 24 ++ .../include/backend/TargetBufferInfo.h | 27 ++- .../backend/platforms/OpenGLPlatform.h | 23 ++ .../backend/platforms/PlatformEGLAndroid.h | 21 ++ .../backend/platforms/VulkanPlatform.h | 49 +++- .../include/filamat/MaterialBuilder.h | 13 ++ .../libs/filament/include/filament/Engine.h | 111 +++++++-- .../include/filament/MaterialChunkType.h | 1 + .../filament/include/filament/MaterialEnums.h | 2 +- .../include/filament/MaterialInstance.h | 5 + .../include/filament/RenderableManager.h | 78 ++++--- .../filament/include/filament/SwapChain.h | 42 ++-- .../ios/libs/filament/include/filament/View.h | 7 + .../filament/include/gltfio/AssetLoader.h | 21 ++ .../include/utils/FixedCapacityVector.h | 16 +- .../ios/libs/filament/include/utils/Panic.h | 211 ++++++++++++++---- .../include/utils/StructureOfArrays.h | 4 +- .../libs/filament/include/utils/algorithm.h | 82 ++++++- .../ios/libs/filament/include/utils/bitset.h | 12 +- .../ios/libs/filament/include/utils/sstream.h | 36 +++ 48 files changed, 1410 insertions(+), 312 deletions(-) create mode 100644 package/android/libs/filament/include/utils/sstream.h create mode 100644 package/ios/libs/filament/include/utils/sstream.h diff --git a/package/android/libs/filament/include/backend/DriverEnums.h b/package/android/libs/filament/include/backend/DriverEnums.h index ef1c655c..d69a9991 100644 --- a/package/android/libs/filament/include/backend/DriverEnums.h +++ b/package/android/libs/filament/include/backend/DriverEnums.h @@ -22,14 +22,17 @@ #include #include // Because we define ERROR in the FenceStatus enum. +#include #include +#include #include #include #include // FIXME: STL headers are not allowed in public headers #include // FIXME: STL headers are not allowed in public headers +#include // FIXME: STL headers are not allowed in public headers #include #include @@ -90,12 +93,15 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON */ static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40; - static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3. static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects. static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte + // of push constant (we assume 4-byte + // types). + // Per feature level caps // Use (int)FeatureLevel to index this array static constexpr struct { @@ -112,7 +118,7 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT, "The number of buffer objects that can be attached to a VertexBuffer must be " "less than or equal to the maximum number of vertex attributes."); -static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 10; // This is guaranteed by OpenGL ES. +static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES. static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES. /** @@ -331,7 +337,7 @@ enum class UniformType : uint8_t { /** * Supported constant parameter types */ - enum class ConstantType : uint8_t { +enum class ConstantType : uint8_t { INT, FLOAT, BOOL @@ -686,7 +692,7 @@ enum class TextureUsage : uint16_t { SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() - PROTECTED = 0x0100, //!< Texture can be used the destination of a blit() + PROTECTED = 0x0100, //!< Texture can be used for protected content DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage }; @@ -1218,13 +1224,15 @@ struct StencilState { uint8_t padding = 0; }; +using PushConstantVariant = std::variant; + static_assert(sizeof(StencilState::StencilOperations) == 5u, "StencilOperations size not what was intended"); static_assert(sizeof(StencilState) == 12u, "StencilState size not what was intended"); -using FrameScheduledCallback = void(*)(PresentCallable callable, void* user); +using FrameScheduledCallback = utils::Invocable; enum class Workaround : uint16_t { // The EASU pass must split because shader compiler flattens early-exit branch @@ -1243,13 +1251,7 @@ enum class Workaround : uint16_t { POWER_VR_SHADER_WORKAROUNDS, }; -//! The type of technique for stereoscopic rendering -enum class StereoscopicType : uint8_t { - // Stereoscopic rendering is performed using instanced rendering technique. - INSTANCED, - // Stereoscopic rendering is performed using the multiview feature from the graphics backend. - MULTIVIEW, -}; +using StereoscopicType = backend::Platform::StereoscopicType; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/Handle.h b/package/android/libs/filament/include/backend/Handle.h index 4b63607a..c54e9609 100644 --- a/package/android/libs/filament/include/backend/Handle.h +++ b/package/android/libs/filament/include/backend/Handle.h @@ -75,6 +75,19 @@ class HandleBase { HandleBase(HandleBase const& rhs) noexcept = default; HandleBase& operator=(HandleBase const& rhs) noexcept = default; + HandleBase(HandleBase&& rhs) noexcept + : object(rhs.object) { + rhs.object = nullid; + } + + HandleBase& operator=(HandleBase&& rhs) noexcept { + if (this != &rhs) { + object = rhs.object; + rhs.object = nullid; + } + return *this; + } + private: HandleId object; }; @@ -89,8 +102,10 @@ struct Handle : public HandleBase { Handle() noexcept = default; Handle(Handle const& rhs) noexcept = default; + Handle(Handle&& rhs) noexcept = default; Handle& operator=(Handle const& rhs) noexcept = default; + Handle& operator=(Handle&& rhs) noexcept = default; explicit Handle(HandleId id) noexcept : HandleBase(id) { } diff --git a/package/android/libs/filament/include/backend/Platform.h b/package/android/libs/filament/include/backend/Platform.h index 03026dff..4f73c437 100644 --- a/package/android/libs/filament/include/backend/Platform.h +++ b/package/android/libs/filament/include/backend/Platform.h @@ -41,6 +41,26 @@ class UTILS_PUBLIC Platform { struct Fence {}; struct Stream {}; + /** + * The type of technique for stereoscopic rendering. (Note that the materials used will need to + * be compatible with the chosen technique.) + */ + enum class StereoscopicType : uint8_t { + /** + * No stereoscopic rendering + */ + NONE, + /** + * Stereoscopic rendering is performed using instanced rendering technique. + */ + INSTANCED, + /** + * Stereoscopic rendering is performed using the multiview feature from the graphics + * backend. + */ + MULTIVIEW, + }; + struct DriverConfig { /** * Size of handle arena in bytes. Setting to 0 indicates default value is to be used. @@ -65,6 +85,17 @@ class UTILS_PUBLIC Platform { * Disable backend handles use-after-free checks. */ bool disableHandleUseAfterFreeCheck = false; + + /** + * Force GLES2 context if supported, or pretend the context is ES2. Only meaningful on + * GLES 3.x backends. + */ + bool forceGLES2Context = false; + + /** + * Sets the technique for stereoscopic rendering. + */ + StereoscopicType stereoscopicType = StereoscopicType::NONE; }; Platform() noexcept; diff --git a/package/android/libs/filament/include/backend/PresentCallable.h b/package/android/libs/filament/include/backend/PresentCallable.h index 4402f222..f37d7704 100644 --- a/package/android/libs/filament/include/backend/PresentCallable.h +++ b/package/android/libs/filament/include/backend/PresentCallable.h @@ -48,7 +48,7 @@ namespace filament::backend { * and optional user data: * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * swapChain->setFrameScheduledCallback(myFrameScheduledCallback, nullptr); + * swapChain->setFrameScheduledCallback(nullptr, myFrameScheduledCallback); * if (renderer->beginFrame(swapChain)) { * renderer->render(view); * renderer->endFrame(); @@ -58,8 +58,6 @@ namespace filament::backend { * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other * backends ignore the callback (which will never be called) and proceed normally. * - * @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread. - * * Applications *must* call each PresentCallable they receive. Each PresentCallable represents a * frame that is waiting to be presented. If an application fails to call a PresentCallable, a * memory leak could occur. To "cancel" the presentation of a frame, pass false to the diff --git a/package/android/libs/filament/include/backend/Program.h b/package/android/libs/filament/include/backend/Program.h index fe1c4a9b..7cec72cd 100644 --- a/package/android/libs/filament/include/backend/Program.h +++ b/package/android/libs/filament/include/backend/Program.h @@ -84,6 +84,9 @@ class Program { // null terminating character. Program& shader(ShaderStage shader, void const* data, size_t size); + // sets the language of the shader sources provided with shader() (defaults to ESSL3) + Program& shaderLanguage(ShaderLanguage shaderLanguage); + // Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is // not permitted in glsl. The backend needs a way to associate a uniform block // to a binding point. @@ -114,6 +117,14 @@ class Program { Program& specializationConstants( utils::FixedCapacityVector specConstants) noexcept; + struct PushConstant { + utils::CString name; + ConstantType type; + }; + + Program& pushConstants(ShaderStage stage, + utils::FixedCapacityVector constants) noexcept; + Program& cacheId(uint64_t cacheId) noexcept; Program& multiview(bool multiview) noexcept; @@ -136,6 +147,8 @@ class Program { utils::CString const& getName() const noexcept { return mName; } utils::CString& getName() noexcept { return mName; } + auto const& getShaderLanguage() const { return mShaderLanguage; } + utils::FixedCapacityVector const& getSpecializationConstants() const noexcept { return mSpecializationConstants; } @@ -143,6 +156,15 @@ class Program { return mSpecializationConstants; } + utils::FixedCapacityVector const& getPushConstants( + ShaderStage stage) const noexcept { + return mPushConstants[static_cast(stage)]; + } + + utils::FixedCapacityVector& getPushConstants(ShaderStage stage) noexcept { + return mPushConstants[static_cast(stage)]; + } + uint64_t getCacheId() const noexcept { return mCacheId; } bool isMultiview() const noexcept { return mMultiview; } @@ -155,10 +177,12 @@ class Program { UniformBlockInfo mUniformBlocks = {}; SamplerGroupInfo mSamplerGroups = {}; ShaderSource mShadersSource; + ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3; utils::CString mName; uint64_t mCacheId{}; utils::Invocable mLogger; utils::FixedCapacityVector mSpecializationConstants; + std::array, SHADER_TYPE_COUNT> mPushConstants; utils::FixedCapacityVector> mAttributes; std::array mBindingUniformInfo; CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; diff --git a/package/android/libs/filament/include/backend/TargetBufferInfo.h b/package/android/libs/filament/include/backend/TargetBufferInfo.h index ce23fc5f..c4d284cc 100644 --- a/package/android/libs/filament/include/backend/TargetBufferInfo.h +++ b/package/android/libs/filament/include/backend/TargetBufferInfo.h @@ -29,17 +29,36 @@ namespace filament::backend { //! \privatesection struct TargetBufferInfo { + // note: the parameters of this constructor are not in the order of this structure's fields + TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept + : handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) { + } + + TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer) noexcept + : handle(handle), level(level), layer(layer) { + } + + TargetBufferInfo(Handle handle, uint8_t level) noexcept + : handle(handle), level(level) { + } + + TargetBufferInfo(Handle handle) noexcept // NOLINT(*-explicit-constructor) + : handle(handle) { + } + + TargetBufferInfo() noexcept = default; + // texture to be used as render target Handle handle; - // starting layer index for multiview. This value is only used when the `layerCount` for the + // Starting layer index for multiview. This value is only used when the `layerCount` for the // render target is greater than 1. uint8_t baseViewIndex = 0; // level to be used uint8_t level = 0; - // for cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping + // For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping uint16_t layer = 0; }; @@ -64,7 +83,7 @@ class MRT { MRT() noexcept = default; - MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions) + MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions, *-explicit-constructor) : mInfos{ color } { } @@ -84,7 +103,7 @@ class MRT { // this is here for backward compatibility MRT(Handle handle, uint8_t level, uint16_t layer) noexcept - : mInfos{{ handle, 0, level, layer }} { + : mInfos{{ handle, level, layer, 0 }} { } }; diff --git a/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h b/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h index dec6f47b..e00930c9 100644 --- a/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h +++ b/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h @@ -140,6 +140,23 @@ class OpenGLPlatform : public Platform { */ virtual uint32_t getDefaultFramebufferObject() noexcept; + /** + * Called by the backend when a frame starts. + * @param steady_clock_ns vsync time point on the monotonic clock + * @param refreshIntervalNs refresh interval in nanosecond + * @param frameId a frame id + */ + virtual void beginFrame( + int64_t monotonic_clock_ns, + int64_t refreshIntervalNs, + uint32_t frameId) noexcept; + + /** + * Called by the backend when a frame ends. + * @param frameId the frame id used in beginFrame + */ + virtual void endFrame( + uint32_t frameId) noexcept; /** * Type of contexts available @@ -191,6 +208,12 @@ class OpenGLPlatform : public Platform { utils::Invocable preContextChange, utils::Invocable postContextChange) noexcept; + /** + * Called by the backend just before calling commit() + * @see commit() + */ + virtual void preCommit() noexcept; + /** * Called by the driver once the current frame finishes drawing. Typically, this should present * the drawSwapChain. This is for example where `eglMakeCurrent()` would be called. diff --git a/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h b/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h index 32f83038..c3cc7da8 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h @@ -22,6 +22,10 @@ #include #include +#include + +#include + #include #include @@ -58,6 +62,13 @@ class PlatformEGLAndroid : public PlatformEGL { void terminate() noexcept override; + void beginFrame( + int64_t monotonic_clock_ns, + int64_t refreshIntervalNs, + uint32_t frameId) noexcept override; + + void preCommit() noexcept override; + /** * Set the presentation time using `eglPresentationTimeANDROID` * @param presentationTimeInNanosecond @@ -79,8 +90,18 @@ class PlatformEGLAndroid : public PlatformEGL { AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override; private: + struct InitializeJvmForPerformanceManagerIfNeeded { + InitializeJvmForPerformanceManagerIfNeeded(); + }; + int mOSVersion; ExternalStreamManagerAndroid& mExternalStreamManager; + InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded; + utils::PerformanceHintManager mPerformanceHintManager; + utils::PerformanceHintManager::Session mPerformanceHintSession; + + using clock = std::chrono::high_resolution_clock; + clock::time_point mStartTimeOfActualWork; }; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h b/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h index 6201d644..2bbe1983 100644 --- a/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h +++ b/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h @@ -23,9 +23,9 @@ #include #include +#include #include -#include #include #include @@ -47,6 +47,14 @@ struct VulkanPlatformPrivate; class VulkanPlatform : public Platform, utils::PrivateImplementation { public: + struct ExtensionHashFn { + std::size_t operator()(utils::CString const& s) const noexcept { + return std::hash{}(s.data()); + } + }; + // Utility for managing device or instance extensions during initialization. + using ExtensionSet = std::unordered_set; + /** * A collection of handles to objects and metadata that comprises a Vulkan context. The client * can instantiate this struct and pass to Engine::Builder::sharedContext if they wishes to @@ -82,6 +90,20 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation explicitImageReadyWait = nullptr; + }; + VulkanPlatform(); ~VulkanPlatform() override; @@ -119,6 +141,12 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation; - static ExtensionSet getRequiredInstanceExtensions(); + static ExtensionSet getSwapchainInstanceExtensions(); + // Platform dependent helper methods using SurfaceBundle = std::tuple; static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, uint64_t flags) noexcept; diff --git a/package/android/libs/filament/include/filamat/MaterialBuilder.h b/package/android/libs/filament/include/filamat/MaterialBuilder.h index 4b66965d..c3045b94 100644 --- a/package/android/libs/filament/include/filamat/MaterialBuilder.h +++ b/package/android/libs/filament/include/filamat/MaterialBuilder.h @@ -245,6 +245,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using CullingMode = filament::backend::CullingMode; using FeatureLevel = filament::backend::FeatureLevel; using StereoscopicType = filament::backend::StereoscopicType; + using ShaderStage = filament::backend::ShaderStage; enum class VariableQualifier : uint8_t { OUT @@ -692,6 +693,12 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { } defaultValue; }; + struct PushConstant { + utils::CString name; + ConstantType type; + ShaderStage stage; + }; + static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT; using Property = filament::Property; @@ -720,6 +727,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using SubpassList = Parameter[MAX_SUBPASS_COUNT]; using BufferList = std::vector>; using ConstantList = std::vector; + using PushConstantList = std::vector; // returns the number of parameters declared in this material uint8_t getParameterCount() const noexcept { return mParameterCount; } @@ -763,6 +771,10 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { void prepareToBuild(MaterialInfo& info) noexcept; + // Initialize internal push constants that will both be written to the shaders and material + // chunks (like user-defined spec constants). + void initPushConstants() noexcept; + // Return true if the shader is syntactically and semantically valid. // This method finds all the properties defined in the fragment and // vertex shaders of the material. @@ -829,6 +841,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { PropertyList mProperties; ParameterList mParameters; ConstantList mConstants; + PushConstantList mPushConstants; SubpassList mSubpasses; VariableList mVariables; OutputList mOutputs; diff --git a/package/android/libs/filament/include/filament/Engine.h b/package/android/libs/filament/include/filament/Engine.h index b741d3ab..2aed8273 100644 --- a/package/android/libs/filament/include/filament/Engine.h +++ b/package/android/libs/filament/include/filament/Engine.h @@ -315,7 +315,7 @@ class UTILS_PUBLIC Engine { * * @see View::setStereoscopicOptions */ - StereoscopicType stereoscopicType = StereoscopicType::INSTANCED; + StereoscopicType stereoscopicType = StereoscopicType::NONE; /* * The number of eyes to render when stereoscopic rendering is enabled. Supported values are @@ -340,6 +340,35 @@ class UTILS_PUBLIC Engine { * Disable backend handles use-after-free checks. */ bool disableHandleUseAfterFreeCheck = false; + + /* + * Sets a preferred shader language for Filament to use. + * + * The Metal backend supports two shader languages: MSL (Metal Shading Language) and + * METAL_LIBRARY (precompiled .metallib). This option controls which shader language is + * used when materials contain both. + * + * By default, when preferredShaderLanguage is unset, Filament will prefer METAL_LIBRARY + * shaders if present within a material, falling back to MSL. Setting + * preferredShaderLanguage to ShaderLanguage::MSL will instead instruct Filament to check + * for the presence of MSL in a material first, falling back to METAL_LIBRARY if MSL is not + * present. + * + * When using a non-Metal backend, setting this has no effect. + */ + enum class ShaderLanguage { + DEFAULT = 0, + MSL = 1, + METAL_LIBRARY = 2, + }; + ShaderLanguage preferredShaderLanguage = ShaderLanguage::DEFAULT; + + /* + * When the OpenGL ES backend is used, setting this value to true will force a GLES2.0 + * context if supported by the Platform, or if not, will have the backend pretend + * it's a GLES2 context. Ignored on other backends. + */ + bool forceGLES2Context = false; }; @@ -800,24 +829,51 @@ class UTILS_PUBLIC Engine { bool destroy(const InstanceBuffer* UTILS_NULLABLE p); //!< Destroys an InstanceBuffer object. void destroy(utils::Entity e); //!< Destroys all filament-known components from this entity - bool isValid(const BufferObject* UTILS_NULLABLE p); //!< Tells whether a BufferObject object is valid - bool isValid(const VertexBuffer* UTILS_NULLABLE p); //!< Tells whether an VertexBuffer object is valid - bool isValid(const Fence* UTILS_NULLABLE p); //!< Tells whether a Fence object is valid - bool isValid(const IndexBuffer* UTILS_NULLABLE p); //!< Tells whether an IndexBuffer object is valid - bool isValid(const SkinningBuffer* UTILS_NULLABLE p); //!< Tells whether a SkinningBuffer object is valid - bool isValid(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Tells whether a MorphTargetBuffer object is valid - bool isValid(const IndirectLight* UTILS_NULLABLE p); //!< Tells whether an IndirectLight object is valid - bool isValid(const Material* UTILS_NULLABLE p); //!< Tells whether an IndirectLight object is valid - bool isValid(const Renderer* UTILS_NULLABLE p); //!< Tells whether a Renderer object is valid - bool isValid(const Scene* UTILS_NULLABLE p); //!< Tells whether a Scene object is valid - bool isValid(const Skybox* UTILS_NULLABLE p); //!< Tells whether a SkyBox object is valid - bool isValid(const ColorGrading* UTILS_NULLABLE p); //!< Tells whether a ColorGrading object is valid - bool isValid(const SwapChain* UTILS_NULLABLE p); //!< Tells whether a SwapChain object is valid - bool isValid(const Stream* UTILS_NULLABLE p); //!< Tells whether a Stream object is valid - bool isValid(const Texture* UTILS_NULLABLE p); //!< Tells whether a Texture object is valid - bool isValid(const RenderTarget* UTILS_NULLABLE p); //!< Tells whether a RenderTarget object is valid - bool isValid(const View* UTILS_NULLABLE p); //!< Tells whether a View object is valid - bool isValid(const InstanceBuffer* UTILS_NULLABLE p); //!< Tells whether an InstanceBuffer object is valid + /** Tells whether a BufferObject object is valid */ + bool isValid(const BufferObject* UTILS_NULLABLE p) const; + /** Tells whether an VertexBuffer object is valid */ + bool isValid(const VertexBuffer* UTILS_NULLABLE p) const; + /** Tells whether a Fence object is valid */ + bool isValid(const Fence* UTILS_NULLABLE p) const; + /** Tells whether an IndexBuffer object is valid */ + bool isValid(const IndexBuffer* UTILS_NULLABLE p) const; + /** Tells whether a SkinningBuffer object is valid */ + bool isValid(const SkinningBuffer* UTILS_NULLABLE p) const; + /** Tells whether a MorphTargetBuffer object is valid */ + bool isValid(const MorphTargetBuffer* UTILS_NULLABLE p) const; + /** Tells whether an IndirectLight object is valid */ + bool isValid(const IndirectLight* UTILS_NULLABLE p) const; + /** Tells whether an Material object is valid */ + bool isValid(const Material* UTILS_NULLABLE p) const; + /** Tells whether an MaterialInstance object is valid. Use this if you already know + * which Material this MaterialInstance belongs to. DO NOT USE getMaterial(), this would + * defeat the purpose of validating the MaterialInstance. + */ + bool isValid(const Material* UTILS_NONNULL m, const MaterialInstance* UTILS_NULLABLE p) const; + /** Tells whether an MaterialInstance object is valid. Use this if the Material the + * MaterialInstance belongs to is not known. This method can be expensive. + */ + bool isValidExpensive(const MaterialInstance* UTILS_NULLABLE p) const; + /** Tells whether a Renderer object is valid */ + bool isValid(const Renderer* UTILS_NULLABLE p) const; + /** Tells whether a Scene object is valid */ + bool isValid(const Scene* UTILS_NULLABLE p) const; + /** Tells whether a SkyBox object is valid */ + bool isValid(const Skybox* UTILS_NULLABLE p) const; + /** Tells whether a ColorGrading object is valid */ + bool isValid(const ColorGrading* UTILS_NULLABLE p) const; + /** Tells whether a SwapChain object is valid */ + bool isValid(const SwapChain* UTILS_NULLABLE p) const; + /** Tells whether a Stream object is valid */ + bool isValid(const Stream* UTILS_NULLABLE p) const; + /** Tells whether a Texture object is valid */ + bool isValid(const Texture* UTILS_NULLABLE p) const; + /** Tells whether a RenderTarget object is valid */ + bool isValid(const RenderTarget* UTILS_NULLABLE p) const; + /** Tells whether a View object is valid */ + bool isValid(const View* UTILS_NULLABLE p) const; + /** Tells whether an InstanceBuffer object is valid */ + bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const; /** * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until @@ -840,6 +896,15 @@ class UTILS_PUBLIC Engine { */ void flush(); + /** + * Get paused state of rendering thread. + * + *

Warning: This is an experimental API. + * + * @see setPaused + */ + bool isPaused() const noexcept; + /** * Pause or resume rendering thread. * @@ -865,6 +930,14 @@ class UTILS_PUBLIC Engine { */ void pumpMessageQueues(); + /** + * Switch the command queue to unprotected mode. Protected mode can be activated via + * Renderer::beginFrame() using a protected SwapChain. + * @see Renderer + * @see SwapChain + */ + void unprotected() noexcept; + /** * Returns the default Material. * diff --git a/package/android/libs/filament/include/filament/MaterialChunkType.h b/package/android/libs/filament/include/filament/MaterialChunkType.h index c80ac7d8..4a4561c1 100644 --- a/package/android/libs/filament/include/filament/MaterialChunkType.h +++ b/package/android/libs/filament/include/filament/MaterialChunkType.h @@ -53,6 +53,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialAttributeInfo = charTo64bitNum("MAT_ATTR"), MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialConstants = charTo64bitNum("MAT_CONS"), + MaterialPushConstants = charTo64bitNum("MAT_PCON"), MaterialName = charTo64bitNum("MAT_NAME"), MaterialVersion = charTo64bitNum("MAT_VERS"), diff --git a/package/android/libs/filament/include/filament/MaterialEnums.h b/package/android/libs/filament/include/filament/MaterialEnums.h index 9f348481..c15c6c45 100644 --- a/package/android/libs/filament/include/filament/MaterialEnums.h +++ b/package/android/libs/filament/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 51; +static constexpr size_t MATERIAL_VERSION = 53; /** * Supported shading models diff --git a/package/android/libs/filament/include/filament/MaterialInstance.h b/package/android/libs/filament/include/filament/MaterialInstance.h index 7252f9a4..2b8aaa9a 100644 --- a/package/android/libs/filament/include/filament/MaterialInstance.h +++ b/package/android/libs/filament/include/filament/MaterialInstance.h @@ -140,6 +140,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @param values Array of values to set to the named parameter array. * @param count Size of the array to set. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. + * @see Material::hasParameter */ template> void setParameter(const char* UTILS_NONNULL name, size_t nameLength, @@ -237,9 +238,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** * Gets the value of a parameter by name. * + * Note: Only supports non-texture parameters such as numeric and math types. + * * @param name Name of the parameter as defined by Material. Cannot be nullptr. * @param nameLength Length in `char` of the name parameter. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. + * + * @see Material::hasParameter */ template T getParameter(const char* UTILS_NONNULL name, size_t nameLength) const; diff --git a/package/android/libs/filament/include/filament/RenderableManager.h b/package/android/libs/filament/include/filament/RenderableManager.h index bb50b7d1..363ef2d7 100644 --- a/package/android/libs/filament/include/filament/RenderableManager.h +++ b/package/android/libs/filament/include/filament/RenderableManager.h @@ -464,15 +464,30 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { Builder& boneIndicesAndWeights(size_t primitiveIndex, utils::FixedCapacityVector< utils::FixedCapacityVector> indicesAndWeightsVector) noexcept; + + /** + * Controls if the renderable has legacy vertex morphing targets, zero by default. This is + * required to enable GPU morphing. + * + * For legacy morphing, the attached VertexBuffer must provide data in the + * appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only + * supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must + * be enabled on the material definition: either via the legacyMorphing material attribute + * or by calling filamat::MaterialBuilder::useLegacyMorphing(). + * + * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis + * to advance the animation. + */ + Builder& morphing(size_t targetCount) noexcept; + /** * Controls if the renderable has vertex morphing targets, zero by default. This is * required to enable GPU morphing. * * Filament supports two morphing modes: standard (default) and legacy. * - * For standard morphing, A MorphTargetBuffer must be created and provided via - * RenderableManager::setMorphTargetBufferAt(). Standard morphing supports up to - * \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets. + * For standard morphing, A MorphTargetBuffer must be provided. + * Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets. * * For legacy morphing, the attached VertexBuffer must provide data in the * appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only @@ -483,29 +498,35 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis * to advance the animation. */ - Builder& morphing(size_t targetCount) noexcept; + Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; /** - * Specifies the morph target buffer for a primitive. - * - * The morph target buffer must have an associated renderable and geometry. Two conditions - * must be met: - * 1. The number of morph targets in the buffer must equal the renderable's morph target - * count. - * 2. The vertex count of each morph target must equal the geometry's vertex count. + * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead + */ + Builder& morphing(uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, + size_t offset, size_t count) noexcept; + + /** + * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead + */ + inline Builder& morphing(uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { + return morphing(level, primitiveIndex, morphTargetBuffer, 0, + morphTargetBuffer->getVertexCount()); + } + + /** + * Specifies the the range of the MorphTargetBuffer to use with this primitive. * * @param level the level of detail (lod), only 0 can be specified * @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor - * @param morphTargetBuffer specifies the morph target buffer * @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices) * @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3) */ Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count) noexcept; - inline Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; /** * Sets the drawing order for blended primitives. The drawing order is either global or @@ -765,14 +786,19 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /** * Associates a MorphTargetBuffer to the given primitive. */ + void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + size_t offset, size_t count); + + /** @deprecated */ void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count); - /** - * Utility method to change a MorphTargetBuffer to the given primitive - */ + /** @deprecated */ inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer); + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { + setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, + morphTargetBuffer->getVertexCount()); + } /** * Get a MorphTargetBuffer to the given primitive or null if it doesn't exist. @@ -906,20 +932,6 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { ~RenderableManager() = default; }; -RenderableManager::Builder& RenderableManager::Builder::morphing( - uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { - return morphing(level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); -} - -void RenderableManager::setMorphTargetBufferAt( - Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { - setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); -} - template Box RenderableManager::computeAABB( VECTOR const* UTILS_NONNULL vertices, diff --git a/package/android/libs/filament/include/filament/SwapChain.h b/package/android/libs/filament/include/filament/SwapChain.h index 0af01afc..6917507a 100644 --- a/package/android/libs/filament/include/filament/SwapChain.h +++ b/package/android/libs/filament/include/filament/SwapChain.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -115,7 +116,7 @@ class Engine; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * SDL_SysWMinfo wmi; * SDL_VERSION(&wmi.version); - * ASSERT_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi), "SDL version unsupported!"); + * FILAMENT_CHECK_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi)) << "SDL version unsupported!"; * HDC nativeWindow = (HDC) wmi.info.win.hdc; * * using namespace filament; @@ -264,13 +265,22 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * backend. * * A FrameScheduledCallback can be set on an individual SwapChain through - * SwapChain::setFrameScheduledCallback. If the callback is set, then the SwapChain will *not* - * automatically schedule itself for presentation. Instead, the application must call the - * PresentCallable passed to the FrameScheduledCallback. + * SwapChain::setFrameScheduledCallback. If the callback is set for a given frame, then the + * SwapChain will *not* automatically schedule itself for presentation. Instead, the application + * must call the PresentCallable passed to the FrameScheduledCallback. * - * There may be only one FrameScheduledCallback set per SwapChain. A call to - * SwapChain::setFrameScheduledCallback will overwrite any previous FrameScheduledCallbacks set - * on the same SwapChain. + * Each SwapChain can have only one FrameScheduledCallback set per frame. If + * setFrameScheduledCallback is called multiple times on the same SwapChain before + * Renderer::endFrame(), the most recent call effectively overwrites any previously set + * callback. This allows the callback to be updated as needed before the frame has finished + * encoding. + * + * The "last" callback set by setFrameScheduledCallback gets "latched" when Renderer::endFrame() + * is executed. At this point, the state of the callback is fixed and is the one used for the + * frame that was just encoded. Subsequent changes to the callback using + * setFrameScheduledCallback after endFrame() apply to the next frame. + * + * Use \c setFrameScheduledCallback() (with default arguments) to unset the callback. * * If your application delays the call to the PresentCallable by, for example, calling it on a * separate thread, you must ensure all PresentCallables have been called before shutting down @@ -278,28 +288,26 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean * up all memory related to frame presentation. * - * @param callback A callback, or nullptr to unset. - * @param user An optional pointer to user data passed to the callback function. + * @param handler Handler to dispatch the callback or nullptr for the default handler. + * @param callback Callback called when the frame is scheduled. * * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other * backends ignore the callback (which will never be called) and proceed normally. * - * @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread. - * + * @see CallbackHandler * @see PresentCallable */ - void setFrameScheduledCallback(FrameScheduledCallback UTILS_NULLABLE callback, - void* UTILS_NULLABLE user = nullptr); + void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, + FrameScheduledCallback&& callback = {}); /** - * Returns the SwapChain::FrameScheduledCallback that was previously set with - * SwapChain::setFrameScheduledCallback, or nullptr if one is not set. + * Returns whether or not this SwapChain currently has a FrameScheduledCallback set. * - * @return the previously-set FrameScheduledCallback, or nullptr + * @return true, if the last call to setFrameScheduledCallback set a callback * * @see SwapChain::setFrameCompletedCallback */ - UTILS_NULLABLE FrameScheduledCallback getFrameScheduledCallback() const noexcept; + bool isFrameScheduledCallbackSet() const noexcept; /** * FrameCompletedCallback is a callback function that notifies an application when a frame's diff --git a/package/android/libs/filament/include/filament/View.h b/package/android/libs/filament/include/filament/View.h index 3cdd527f..1a1ed96f 100644 --- a/package/android/libs/filament/include/filament/View.h +++ b/package/android/libs/filament/include/filament/View.h @@ -189,6 +189,13 @@ class UTILS_PUBLIC View : public FilamentAPI { */ void setCamera(Camera* UTILS_NONNULL camera) noexcept; + /** + * Returns whether a Camera is set. + * @return true if a camera is set. + * @see setCamera() + */ + bool hasCamera() const noexcept; + /** * Returns the Camera currently associated with this View. * @return A reference to the Camera associated to this View. diff --git a/package/android/libs/filament/include/gltfio/AssetLoader.h b/package/android/libs/filament/include/gltfio/AssetLoader.h index f516166a..bf650f69 100644 --- a/package/android/libs/filament/include/gltfio/AssetLoader.h +++ b/package/android/libs/filament/include/gltfio/AssetLoader.h @@ -38,6 +38,23 @@ namespace filament::gltfio { class NodeManager; +// Use this struct to enable mikktspace-based tangent-space computation. +/** + * \struct AssetConfigurationExtended AssetLoader.h gltfio/AssetLoader.h + * \brief extends struct AssetConfiguration + * Useful if client needs mikktspace tangent space computation. + * NOTE: Android, iOS, Web are not supported. And only disk-local glTF resources are supported. + */ +struct AssetConfigurationExtended { + //! Optional The same parameter as provided to \struct ResourceConfiguration ResourceLoader.h + //! gltfio/ResourceLoader.h + char const* gltfPath; + + //! Client can use this method to check if the extended implementation is supported on their + //! platform or not. + static bool isSupported(); +}; + /** * \struct AssetConfiguration AssetLoader.h gltfio/AssetLoader.h * \brief Construction parameters for AssetLoader. @@ -62,6 +79,10 @@ struct AssetConfiguration { //! Optional default node name for anonymous nodes char* defaultNodeName = nullptr; + + //! Optional to enable mikktspace tangents. Lifetime of struct only needs to be maintained for + // the duration of the constructor of AssetLoader. + AssetConfigurationExtended* ext = nullptr; }; /** diff --git a/package/android/libs/filament/include/utils/FixedCapacityVector.h b/package/android/libs/filament/include/utils/FixedCapacityVector.h index f3990124..1221e7cc 100644 --- a/package/android/libs/filament/include/utils/FixedCapacityVector.h +++ b/package/android/libs/filament/include/utils/FixedCapacityVector.h @@ -299,6 +299,16 @@ class UTILS_PUBLIC FixedCapacityVector { } } + UTILS_NOINLINE + void shrink_to_fit() { + if (size() < capacity()) { + FixedCapacityVector t(construct_with_capacity, size(), allocator()); + t.mSize = size(); + std::uninitialized_move(begin(), end(), t.begin()); + this->swap(t); + } + } + private: enum construct_with_capacity_tag{ construct_with_capacity }; @@ -318,9 +328,9 @@ class UTILS_PUBLIC FixedCapacityVector { iterator assertCapacityForSize(size_type s) { if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) { - ASSERT_PRECONDITION(capacity() >= s, - "capacity exceeded: requested size %lu, available capacity %lu.", - (unsigned long)s, (unsigned long)capacity()); + FILAMENT_CHECK_PRECONDITION(capacity() >= s) + << "capacity exceeded: requested size " << (unsigned long)s + << "u, available capacity " << (unsigned long)capacity() << "u."; } return end(); } diff --git a/package/android/libs/filament/include/utils/Panic.h b/package/android/libs/filament/include/utils/Panic.h index c658da4b..6e9ac3f4 100644 --- a/package/android/libs/filament/include/utils/Panic.h +++ b/package/android/libs/filament/include/utils/Panic.h @@ -17,14 +17,28 @@ #ifndef TNT_UTILS_PANIC_H #define TNT_UTILS_PANIC_H +#ifdef FILAMENT_PANIC_USES_ABSL +# if FILAMENT_PANIC_USES_ABSL +# include "absl/log/log.h" +# define FILAMENT_CHECK_PRECONDITION CHECK +# define FILAMENT_CHECK_POSTCONDITION CHECK +# define FILAMENT_CHECK_ARITHMETIC CHECK +# endif +#endif + #include #include +#include #include +#include #ifdef __EXCEPTIONS # define UTILS_EXCEPTIONS 1 #else +# ifdef UTILS_EXCEPTIONS +# error UTILS_EXCEPTIONS is already defined! +# endif #endif /** @@ -280,12 +294,24 @@ class UTILS_PUBLIC Panic { */ virtual const char* what() const noexcept = 0; + /** + * Get the type of the panic (e.g. "Precondition") + * @return a C string containing the type of panic + */ + virtual const char* getType() const noexcept = 0; + /** * Get the reason string for the panic * @return a C string containing the reason for the panic */ virtual const char* getReason() const noexcept = 0; + /** + * Get a version of the reason string that is guaranteed to be constructed from literal + * strings only; it will contain no runtime information. + */ + virtual const char* getReasonLiteral() const noexcept = 0; + /** * Get the function name where the panic was detected. On debug build the fully qualified * function name is returned; on release builds only the function name is. @@ -334,7 +360,9 @@ class UTILS_PUBLIC TPanic : public Panic { const char* what() const noexcept override; // Panic interface + const char* getType() const noexcept override; const char* getReason() const noexcept override; + const char* getReasonLiteral() const noexcept override; const char* getFunction() const noexcept override; const char* getFile() const noexcept override; int getLine() const noexcept override; @@ -348,13 +376,14 @@ class UTILS_PUBLIC TPanic : public Panic { * @param function the name of the function where the error was detected * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected + * @param literal a literal version of the error message * @param format printf style string describing the error * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() */ - static void panic(char const* function, char const* file, int line, const char* format, ...) - UTILS_NORETURN; + static void panic(char const* function, char const* file, int line, char const* literal, + const char* format, ...) UTILS_NORETURN; /** * Depending on the mode set, either throws an exception of type T with the given reason plus @@ -363,43 +392,41 @@ class UTILS_PUBLIC TPanic : public Panic { * @param function the name of the function where the error was detected * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected - * @param s std::string describing the error + * @param literal a literal version of the error message + * @param reason std::string describing the error * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() */ - static inline void panic(char const* function, char const* file, int line, const std::string& s) - UTILS_NORETURN { - panic(function, file, line, s.c_str()); - } + static inline void panic( + char const* function, char const* file, int line, char const* literal, + std::string reason) UTILS_NORETURN; protected: - /** - * Creates a Panic. - * @param reason a description of the cause of the error - */ - explicit TPanic(std::string reason); /** * Creates a Panic with extra information about the error-site. * @param function the name of the function where the error was detected * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected + * @param literal a literal version of the error message * @param reason a description of the cause of the error */ - TPanic(char const* function, char const* file, int line, std::string reason); + TPanic(char const* function, char const* file, int line, char const* literal, + std::string reason); ~TPanic() override; private: void buildMessage(); - CallStack m_callstack; - std::string m_reason; - char const* const m_function = nullptr; - char const* const m_file = nullptr; - const int m_line = -1; - mutable std::string m_msg; + char const* const mFile = nullptr; // file where the panic happened + char const* const mFunction = nullptr; // function where the panic happened + int const mLine = -1; // line where the panic happened + std::string mLiteral; // reason for the panic, built only from literals + std::string mReason; // reason for the panic + mutable std::string mWhat; // fully formatted reason + CallStack mCallstack; }; namespace details { @@ -421,6 +448,7 @@ class UTILS_PUBLIC PreconditionPanic : public TPanic { // e.g.: invalid arguments using TPanic::TPanic; friend class TPanic; + constexpr static auto type = "Precondition"; }; /** @@ -434,6 +462,7 @@ class UTILS_PUBLIC PostconditionPanic : public TPanic { // e.g.: dead-lock would occur, arithmetic errors using TPanic::TPanic; friend class TPanic; + constexpr static auto type = "Postcondition"; }; /** @@ -447,8 +476,74 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { // e.g.: underflow, overflow, internal computations errors using TPanic::TPanic; friend class TPanic; + constexpr static auto type = "Arithmetic"; +}; + +namespace details { + +struct Voidify final { + template + void operator&&(const T&) const&& {} +}; + +class UTILS_PUBLIC PanicStream { +public: + PanicStream( + char const* function, + char const* file, + int line, + char const* message) noexcept; + + ~PanicStream(); + + PanicStream& operator<<(short value) noexcept; + PanicStream& operator<<(unsigned short value) noexcept; + + PanicStream& operator<<(char value) noexcept; + PanicStream& operator<<(unsigned char value) noexcept; + + PanicStream& operator<<(int value) noexcept; + PanicStream& operator<<(unsigned int value) noexcept; + + PanicStream& operator<<(long value) noexcept; + PanicStream& operator<<(unsigned long value) noexcept; + + PanicStream& operator<<(long long value) noexcept; + PanicStream& operator<<(unsigned long long value) noexcept; + + PanicStream& operator<<(float value) noexcept; + PanicStream& operator<<(double value) noexcept; + PanicStream& operator<<(long double value) noexcept; + + PanicStream& operator<<(bool value) noexcept; + + PanicStream& operator<<(const void* value) noexcept; + + PanicStream& operator<<(const char* string) noexcept; + PanicStream& operator<<(const unsigned char* string) noexcept; + + PanicStream& operator<<(std::string const& s) noexcept; + PanicStream& operator<<(std::string_view const& s) noexcept; + +protected: + io::sstream mStream; + char const* mFunction; + char const* mFile; + int mLine; + char const* mLiteral; }; +template +class TPanicStream : public PanicStream { +public: + using PanicStream::PanicStream; + ~TPanicStream() noexcept(false) UTILS_NORETURN { + T::panic(mFunction, mFile, mLine, mLiteral, mStream.c_str()); + } +}; + +} // namespace details + // ----------------------------------------------------------------------------------------------- } // namespace utils @@ -460,37 +555,70 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { # define PANIC_FUNCTION __func__ #endif + +#define FILAMENT_CHECK_CONDITION_IMPL(cond) \ + switch (0) \ + case 0: \ + default: \ + UTILS_LIKELY(cond) ? (void)0 : ::utils::details::Voidify()&& + +#define FILAMENT_PANIC_IMPL(message, TYPE) \ + ::utils::details::TPanicStream<::utils::TYPE>(PANIC_FUNCTION, PANIC_FILE(__FILE__), __LINE__, message) + +#ifndef FILAMENT_CHECK_PRECONDITION +#define FILAMENT_CHECK_PRECONDITION(condition) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, PreconditionPanic) +#endif + +#ifndef FILAMENT_CHECK_POSTCONDITION +#define FILAMENT_CHECK_POSTCONDITION(condition) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, PostconditionPanic) +#endif + +#ifndef FILAMENT_CHECK_ARITHMETIC +#define FILAMENT_CHECK_ARITHMETIC(condition) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, ArithmeticPanic) +#endif + +#define PANIC_PRECONDITION_IMPL(cond, format, ...) \ + ::utils::PreconditionPanic::panic(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) + +#define PANIC_POSTCONDITION_IMPL(cond, format, ...) \ + ::utils::PostconditionPanic::panic(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) + +#define PANIC_ARITHMETIC_IMPL(cond, format, ...) \ + ::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) + +#define PANIC_LOG_IMPL(cond, format, ...) \ + ::utils::details::panicLog(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) + /** * PANIC_PRECONDITION is a macro that reports a PreconditionPanic * @param format printf-style string describing the error in more details */ -#define PANIC_PRECONDITION(format, ...) \ - ::utils::PreconditionPanic::panic(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_PRECONDITION(format, ...) PANIC_PRECONDITION_IMPL(format, format, ##__VA_ARGS__) /** * PANIC_POSTCONDITION is a macro that reports a PostconditionPanic * @param format printf-style string describing the error in more details */ -#define PANIC_POSTCONDITION(format, ...) \ - ::utils::PostconditionPanic::panic(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_POSTCONDITION(format, ...) PANIC_POSTCONDITION_IMPL(format, format, ##__VA_ARGS__) /** * PANIC_ARITHMETIC is a macro that reports a ArithmeticPanic * @param format printf-style string describing the error in more details */ -#define PANIC_ARITHMETIC(format, ...) \ - ::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_ARITHMETIC(format, ...) PANIC_ARITHMETIC_IMPL(format, format, ##__VA_ARGS__) /** * PANIC_LOG is a macro that logs a Panic, and continues as usual. * @param format printf-style string describing the error in more details */ -#define PANIC_LOG(format, ...) \ - ::utils::details::panicLog(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_LOG(format, ...) PANIC_LOG_IMPL(format, format, ##__VA_ARGS__) /** * @ingroup errors @@ -501,14 +629,14 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * @param format printf-style string describing the error in more details */ #define ASSERT_PRECONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__) : (void)0) + (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif @@ -529,14 +657,14 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * @endcode */ #define ASSERT_POSTCONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__) : (void)0) + (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -557,14 +685,14 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * @endcode */ #define ASSERT_ARITHMETIC(cond, format, ...) \ - (!(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__) : (void)0) + (!(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -588,6 +716,7 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * } * @endcode */ -#define ASSERT_DESTRUCTOR(cond, format, ...) (!(cond) ? PANIC_LOG(format, ##__VA_ARGS__) : (void)0) +#define ASSERT_DESTRUCTOR(cond, format, ...) \ + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #endif // TNT_UTILS_PANIC_H diff --git a/package/android/libs/filament/include/utils/StructureOfArrays.h b/package/android/libs/filament/include/utils/StructureOfArrays.h index a4309584..c0b2315e 100644 --- a/package/android/libs/filament/include/utils/StructureOfArrays.h +++ b/package/android/libs/filament/include/utils/StructureOfArrays.h @@ -368,7 +368,7 @@ class StructureOfArraysBase { size_t last = mSize++; // Fold expression on the comma operator ([&]{ - new(std::get(mArrays) + last) Elements{std::get(args)}; + new(std::get(mArrays) + last) Elements{std::get(std::forward(args))}; }() , ...); } @@ -513,7 +513,7 @@ class StructureOfArraysBase { return (soa.elementAt(i) = other); } UTILS_ALWAYS_INLINE Type const& operator = (Type&& other) noexcept { - return (soa.elementAt(i) = other); + return (soa.elementAt(i) = std::forward(other)); } // comparisons UTILS_ALWAYS_INLINE bool operator==(Type const& other) const { diff --git a/package/android/libs/filament/include/utils/algorithm.h b/package/android/libs/filament/include/utils/algorithm.h index ea5ca44f..7a747b84 100644 --- a/package/android/libs/filament/include/utils/algorithm.h +++ b/package/android/libs/filament/include/utils/algorithm.h @@ -22,6 +22,7 @@ #include // for std::enable_if #include +#include #include namespace utils { @@ -43,9 +44,15 @@ constexpr inline T clz(T x) noexcept { static_assert(sizeof(T) * CHAR_BIT <= 128, "details::clz() only support up to 128 bits"); x |= (x >> 1u); x |= (x >> 2u); - x |= (x >> 4u); - x |= (x >> 8u); - x |= (x >> 16u); + if constexpr (sizeof(T) * CHAR_BIT >= 8) { // just to silence compiler warning + x |= (x >> 4u); + } + if constexpr (sizeof(T) * CHAR_BIT >= 16) { // just to silence compiler warning + x |= (x >> 8u); + } + if constexpr (sizeof(T) * CHAR_BIT >= 32) { // just to silence compiler warning + x |= (x >> 16u); + } if constexpr (sizeof(T) * CHAR_BIT >= 64) { // just to silence compiler warning x |= (x >> 32u); } @@ -67,11 +74,15 @@ constexpr inline T ctz(T x) noexcept { x &= -x; #endif if (x) c--; - if (sizeof(T) * CHAR_BIT >= 64) { + if constexpr (sizeof(T) * CHAR_BIT >= 64) { if (x & T(0x00000000FFFFFFFF)) c -= 32; } - if (x & T(0x0000FFFF0000FFFF)) c -= 16; - if (x & T(0x00FF00FF00FF00FF)) c -= 8; + if constexpr (sizeof(T) * CHAR_BIT >= 32) { + if (x & T(0x0000FFFF0000FFFF)) c -= 16; + } + if constexpr (sizeof(T) * CHAR_BIT >= 16) { + if (x & T(0x00FF00FF00FF00FF)) c -= 8; + } if (x & T(0x0F0F0F0F0F0F0F0F)) c -= 4; if (x & T(0x3333333333333333)) c -= 2; if (x & T(0x5555555555555555)) c -= 1; @@ -80,6 +91,24 @@ constexpr inline T ctz(T x) noexcept { } // namespace details +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE clz(unsigned char x) noexcept { +#if __has_builtin(__builtin_clz) + return __builtin_clz((unsigned int)x) - 24; +#else + return details::clz(x); +#endif +} + +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE clz(unsigned short x) noexcept { +#if __has_builtin(__builtin_clz) + return __builtin_clz((unsigned int)x) - 16; +#else + return details::clz(x); +#endif +} + constexpr inline UTILS_PUBLIC UTILS_PURE unsigned int UTILS_ALWAYS_INLINE clz(unsigned int x) noexcept { #if __has_builtin(__builtin_clz) @@ -107,6 +136,24 @@ unsigned long long UTILS_ALWAYS_INLINE clz(unsigned long long x) noexcept { #endif } +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE ctz(unsigned char x) noexcept { +#if __has_builtin(__builtin_ctz) + return __builtin_ctz(x); +#else + return details::ctz(x); +#endif +} + +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE ctz(unsigned short x) noexcept { +#if __has_builtin(__builtin_ctz) + return __builtin_ctz(x); +#else + return details::ctz(x); +#endif +} + constexpr inline UTILS_PUBLIC UTILS_PURE unsigned int UTILS_ALWAYS_INLINE ctz(unsigned int x) noexcept { #if __has_builtin(__builtin_ctz) @@ -134,6 +181,24 @@ unsigned long long UTILS_ALWAYS_INLINE ctz(unsigned long long x) noexcept { #endif } +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE popcount(unsigned char x) noexcept { +#if __has_builtin(__builtin_popcount) + return __builtin_popcount(x); +#else + return details::popcount(x); +#endif +} + +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE popcount(unsigned short x) noexcept { +#if __has_builtin(__builtin_popcount) + return __builtin_popcount(x); +#else + return details::popcount(x); +#endif +} + constexpr inline UTILS_PUBLIC UTILS_PURE unsigned int UTILS_ALWAYS_INLINE popcount(unsigned int x) noexcept { #if __has_builtin(__builtin_popcount) @@ -161,11 +226,6 @@ unsigned long long UTILS_ALWAYS_INLINE popcount(unsigned long long x) noexcept { #endif } -constexpr inline UTILS_PUBLIC UTILS_PURE -uint8_t UTILS_ALWAYS_INLINE popcount(uint8_t x) noexcept { - return (uint8_t)popcount((unsigned int)x); -} - template::value && std::is_unsigned::value>> constexpr inline UTILS_PUBLIC UTILS_PURE diff --git a/package/android/libs/filament/include/utils/bitset.h b/package/android/libs/filament/include/utils/bitset.h index 281e5dfc..8844fdb8 100644 --- a/package/android/libs/filament/include/utils/bitset.h +++ b/package/android/libs/filament/include/utils/bitset.h @@ -60,6 +60,11 @@ class UTILS_PUBLIC bitset { std::fill(std::begin(storage), std::end(storage), 0); } + template> + explicit bitset(U value) noexcept { + storage[0] = value; + } + T getBitsAt(size_t n) const noexcept { assert_invariant(n +#include + +#include + +namespace utils::io { + +class UTILS_PUBLIC sstream : public ostream { +public: + ostream& flush() noexcept override; + const char* c_str() const noexcept; + size_t length() const noexcept; +}; + +} // namespace utils::io + +#endif // TNT_UTILS_SSTREAM_H diff --git a/package/ios/libs/filament/include/backend/DriverEnums.h b/package/ios/libs/filament/include/backend/DriverEnums.h index ef1c655c..d69a9991 100644 --- a/package/ios/libs/filament/include/backend/DriverEnums.h +++ b/package/ios/libs/filament/include/backend/DriverEnums.h @@ -22,14 +22,17 @@ #include #include // Because we define ERROR in the FenceStatus enum. +#include #include +#include #include #include #include // FIXME: STL headers are not allowed in public headers #include // FIXME: STL headers are not allowed in public headers +#include // FIXME: STL headers are not allowed in public headers #include #include @@ -90,12 +93,15 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON */ static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40; - static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3. static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects. static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte + // of push constant (we assume 4-byte + // types). + // Per feature level caps // Use (int)FeatureLevel to index this array static constexpr struct { @@ -112,7 +118,7 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT, "The number of buffer objects that can be attached to a VertexBuffer must be " "less than or equal to the maximum number of vertex attributes."); -static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 10; // This is guaranteed by OpenGL ES. +static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES. static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES. /** @@ -331,7 +337,7 @@ enum class UniformType : uint8_t { /** * Supported constant parameter types */ - enum class ConstantType : uint8_t { +enum class ConstantType : uint8_t { INT, FLOAT, BOOL @@ -686,7 +692,7 @@ enum class TextureUsage : uint16_t { SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() - PROTECTED = 0x0100, //!< Texture can be used the destination of a blit() + PROTECTED = 0x0100, //!< Texture can be used for protected content DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage }; @@ -1218,13 +1224,15 @@ struct StencilState { uint8_t padding = 0; }; +using PushConstantVariant = std::variant; + static_assert(sizeof(StencilState::StencilOperations) == 5u, "StencilOperations size not what was intended"); static_assert(sizeof(StencilState) == 12u, "StencilState size not what was intended"); -using FrameScheduledCallback = void(*)(PresentCallable callable, void* user); +using FrameScheduledCallback = utils::Invocable; enum class Workaround : uint16_t { // The EASU pass must split because shader compiler flattens early-exit branch @@ -1243,13 +1251,7 @@ enum class Workaround : uint16_t { POWER_VR_SHADER_WORKAROUNDS, }; -//! The type of technique for stereoscopic rendering -enum class StereoscopicType : uint8_t { - // Stereoscopic rendering is performed using instanced rendering technique. - INSTANCED, - // Stereoscopic rendering is performed using the multiview feature from the graphics backend. - MULTIVIEW, -}; +using StereoscopicType = backend::Platform::StereoscopicType; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/Handle.h b/package/ios/libs/filament/include/backend/Handle.h index 4b63607a..c54e9609 100644 --- a/package/ios/libs/filament/include/backend/Handle.h +++ b/package/ios/libs/filament/include/backend/Handle.h @@ -75,6 +75,19 @@ class HandleBase { HandleBase(HandleBase const& rhs) noexcept = default; HandleBase& operator=(HandleBase const& rhs) noexcept = default; + HandleBase(HandleBase&& rhs) noexcept + : object(rhs.object) { + rhs.object = nullid; + } + + HandleBase& operator=(HandleBase&& rhs) noexcept { + if (this != &rhs) { + object = rhs.object; + rhs.object = nullid; + } + return *this; + } + private: HandleId object; }; @@ -89,8 +102,10 @@ struct Handle : public HandleBase { Handle() noexcept = default; Handle(Handle const& rhs) noexcept = default; + Handle(Handle&& rhs) noexcept = default; Handle& operator=(Handle const& rhs) noexcept = default; + Handle& operator=(Handle&& rhs) noexcept = default; explicit Handle(HandleId id) noexcept : HandleBase(id) { } diff --git a/package/ios/libs/filament/include/backend/Platform.h b/package/ios/libs/filament/include/backend/Platform.h index 03026dff..4f73c437 100644 --- a/package/ios/libs/filament/include/backend/Platform.h +++ b/package/ios/libs/filament/include/backend/Platform.h @@ -41,6 +41,26 @@ class UTILS_PUBLIC Platform { struct Fence {}; struct Stream {}; + /** + * The type of technique for stereoscopic rendering. (Note that the materials used will need to + * be compatible with the chosen technique.) + */ + enum class StereoscopicType : uint8_t { + /** + * No stereoscopic rendering + */ + NONE, + /** + * Stereoscopic rendering is performed using instanced rendering technique. + */ + INSTANCED, + /** + * Stereoscopic rendering is performed using the multiview feature from the graphics + * backend. + */ + MULTIVIEW, + }; + struct DriverConfig { /** * Size of handle arena in bytes. Setting to 0 indicates default value is to be used. @@ -65,6 +85,17 @@ class UTILS_PUBLIC Platform { * Disable backend handles use-after-free checks. */ bool disableHandleUseAfterFreeCheck = false; + + /** + * Force GLES2 context if supported, or pretend the context is ES2. Only meaningful on + * GLES 3.x backends. + */ + bool forceGLES2Context = false; + + /** + * Sets the technique for stereoscopic rendering. + */ + StereoscopicType stereoscopicType = StereoscopicType::NONE; }; Platform() noexcept; diff --git a/package/ios/libs/filament/include/backend/PresentCallable.h b/package/ios/libs/filament/include/backend/PresentCallable.h index 4402f222..f37d7704 100644 --- a/package/ios/libs/filament/include/backend/PresentCallable.h +++ b/package/ios/libs/filament/include/backend/PresentCallable.h @@ -48,7 +48,7 @@ namespace filament::backend { * and optional user data: * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * swapChain->setFrameScheduledCallback(myFrameScheduledCallback, nullptr); + * swapChain->setFrameScheduledCallback(nullptr, myFrameScheduledCallback); * if (renderer->beginFrame(swapChain)) { * renderer->render(view); * renderer->endFrame(); @@ -58,8 +58,6 @@ namespace filament::backend { * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other * backends ignore the callback (which will never be called) and proceed normally. * - * @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread. - * * Applications *must* call each PresentCallable they receive. Each PresentCallable represents a * frame that is waiting to be presented. If an application fails to call a PresentCallable, a * memory leak could occur. To "cancel" the presentation of a frame, pass false to the diff --git a/package/ios/libs/filament/include/backend/Program.h b/package/ios/libs/filament/include/backend/Program.h index fe1c4a9b..7cec72cd 100644 --- a/package/ios/libs/filament/include/backend/Program.h +++ b/package/ios/libs/filament/include/backend/Program.h @@ -84,6 +84,9 @@ class Program { // null terminating character. Program& shader(ShaderStage shader, void const* data, size_t size); + // sets the language of the shader sources provided with shader() (defaults to ESSL3) + Program& shaderLanguage(ShaderLanguage shaderLanguage); + // Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is // not permitted in glsl. The backend needs a way to associate a uniform block // to a binding point. @@ -114,6 +117,14 @@ class Program { Program& specializationConstants( utils::FixedCapacityVector specConstants) noexcept; + struct PushConstant { + utils::CString name; + ConstantType type; + }; + + Program& pushConstants(ShaderStage stage, + utils::FixedCapacityVector constants) noexcept; + Program& cacheId(uint64_t cacheId) noexcept; Program& multiview(bool multiview) noexcept; @@ -136,6 +147,8 @@ class Program { utils::CString const& getName() const noexcept { return mName; } utils::CString& getName() noexcept { return mName; } + auto const& getShaderLanguage() const { return mShaderLanguage; } + utils::FixedCapacityVector const& getSpecializationConstants() const noexcept { return mSpecializationConstants; } @@ -143,6 +156,15 @@ class Program { return mSpecializationConstants; } + utils::FixedCapacityVector const& getPushConstants( + ShaderStage stage) const noexcept { + return mPushConstants[static_cast(stage)]; + } + + utils::FixedCapacityVector& getPushConstants(ShaderStage stage) noexcept { + return mPushConstants[static_cast(stage)]; + } + uint64_t getCacheId() const noexcept { return mCacheId; } bool isMultiview() const noexcept { return mMultiview; } @@ -155,10 +177,12 @@ class Program { UniformBlockInfo mUniformBlocks = {}; SamplerGroupInfo mSamplerGroups = {}; ShaderSource mShadersSource; + ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3; utils::CString mName; uint64_t mCacheId{}; utils::Invocable mLogger; utils::FixedCapacityVector mSpecializationConstants; + std::array, SHADER_TYPE_COUNT> mPushConstants; utils::FixedCapacityVector> mAttributes; std::array mBindingUniformInfo; CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; diff --git a/package/ios/libs/filament/include/backend/TargetBufferInfo.h b/package/ios/libs/filament/include/backend/TargetBufferInfo.h index ce23fc5f..c4d284cc 100644 --- a/package/ios/libs/filament/include/backend/TargetBufferInfo.h +++ b/package/ios/libs/filament/include/backend/TargetBufferInfo.h @@ -29,17 +29,36 @@ namespace filament::backend { //! \privatesection struct TargetBufferInfo { + // note: the parameters of this constructor are not in the order of this structure's fields + TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept + : handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) { + } + + TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer) noexcept + : handle(handle), level(level), layer(layer) { + } + + TargetBufferInfo(Handle handle, uint8_t level) noexcept + : handle(handle), level(level) { + } + + TargetBufferInfo(Handle handle) noexcept // NOLINT(*-explicit-constructor) + : handle(handle) { + } + + TargetBufferInfo() noexcept = default; + // texture to be used as render target Handle handle; - // starting layer index for multiview. This value is only used when the `layerCount` for the + // Starting layer index for multiview. This value is only used when the `layerCount` for the // render target is greater than 1. uint8_t baseViewIndex = 0; // level to be used uint8_t level = 0; - // for cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping + // For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping uint16_t layer = 0; }; @@ -64,7 +83,7 @@ class MRT { MRT() noexcept = default; - MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions) + MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions, *-explicit-constructor) : mInfos{ color } { } @@ -84,7 +103,7 @@ class MRT { // this is here for backward compatibility MRT(Handle handle, uint8_t level, uint16_t layer) noexcept - : mInfos{{ handle, 0, level, layer }} { + : mInfos{{ handle, level, layer, 0 }} { } }; diff --git a/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h b/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h index dec6f47b..e00930c9 100644 --- a/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h +++ b/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h @@ -140,6 +140,23 @@ class OpenGLPlatform : public Platform { */ virtual uint32_t getDefaultFramebufferObject() noexcept; + /** + * Called by the backend when a frame starts. + * @param steady_clock_ns vsync time point on the monotonic clock + * @param refreshIntervalNs refresh interval in nanosecond + * @param frameId a frame id + */ + virtual void beginFrame( + int64_t monotonic_clock_ns, + int64_t refreshIntervalNs, + uint32_t frameId) noexcept; + + /** + * Called by the backend when a frame ends. + * @param frameId the frame id used in beginFrame + */ + virtual void endFrame( + uint32_t frameId) noexcept; /** * Type of contexts available @@ -191,6 +208,12 @@ class OpenGLPlatform : public Platform { utils::Invocable preContextChange, utils::Invocable postContextChange) noexcept; + /** + * Called by the backend just before calling commit() + * @see commit() + */ + virtual void preCommit() noexcept; + /** * Called by the driver once the current frame finishes drawing. Typically, this should present * the drawSwapChain. This is for example where `eglMakeCurrent()` would be called. diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h b/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h index 32f83038..c3cc7da8 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h @@ -22,6 +22,10 @@ #include #include +#include + +#include + #include #include @@ -58,6 +62,13 @@ class PlatformEGLAndroid : public PlatformEGL { void terminate() noexcept override; + void beginFrame( + int64_t monotonic_clock_ns, + int64_t refreshIntervalNs, + uint32_t frameId) noexcept override; + + void preCommit() noexcept override; + /** * Set the presentation time using `eglPresentationTimeANDROID` * @param presentationTimeInNanosecond @@ -79,8 +90,18 @@ class PlatformEGLAndroid : public PlatformEGL { AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override; private: + struct InitializeJvmForPerformanceManagerIfNeeded { + InitializeJvmForPerformanceManagerIfNeeded(); + }; + int mOSVersion; ExternalStreamManagerAndroid& mExternalStreamManager; + InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded; + utils::PerformanceHintManager mPerformanceHintManager; + utils::PerformanceHintManager::Session mPerformanceHintSession; + + using clock = std::chrono::high_resolution_clock; + clock::time_point mStartTimeOfActualWork; }; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h b/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h index 6201d644..2bbe1983 100644 --- a/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h +++ b/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h @@ -23,9 +23,9 @@ #include #include +#include #include -#include #include #include @@ -47,6 +47,14 @@ struct VulkanPlatformPrivate; class VulkanPlatform : public Platform, utils::PrivateImplementation { public: + struct ExtensionHashFn { + std::size_t operator()(utils::CString const& s) const noexcept { + return std::hash{}(s.data()); + } + }; + // Utility for managing device or instance extensions during initialization. + using ExtensionSet = std::unordered_set; + /** * A collection of handles to objects and metadata that comprises a Vulkan context. The client * can instantiate this struct and pass to Engine::Builder::sharedContext if they wishes to @@ -82,6 +90,20 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation explicitImageReadyWait = nullptr; + }; + VulkanPlatform(); ~VulkanPlatform() override; @@ -119,6 +141,12 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation; - static ExtensionSet getRequiredInstanceExtensions(); + static ExtensionSet getSwapchainInstanceExtensions(); + // Platform dependent helper methods using SurfaceBundle = std::tuple; static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, uint64_t flags) noexcept; diff --git a/package/ios/libs/filament/include/filamat/MaterialBuilder.h b/package/ios/libs/filament/include/filamat/MaterialBuilder.h index 4b66965d..c3045b94 100644 --- a/package/ios/libs/filament/include/filamat/MaterialBuilder.h +++ b/package/ios/libs/filament/include/filamat/MaterialBuilder.h @@ -245,6 +245,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using CullingMode = filament::backend::CullingMode; using FeatureLevel = filament::backend::FeatureLevel; using StereoscopicType = filament::backend::StereoscopicType; + using ShaderStage = filament::backend::ShaderStage; enum class VariableQualifier : uint8_t { OUT @@ -692,6 +693,12 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { } defaultValue; }; + struct PushConstant { + utils::CString name; + ConstantType type; + ShaderStage stage; + }; + static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT; using Property = filament::Property; @@ -720,6 +727,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using SubpassList = Parameter[MAX_SUBPASS_COUNT]; using BufferList = std::vector>; using ConstantList = std::vector; + using PushConstantList = std::vector; // returns the number of parameters declared in this material uint8_t getParameterCount() const noexcept { return mParameterCount; } @@ -763,6 +771,10 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { void prepareToBuild(MaterialInfo& info) noexcept; + // Initialize internal push constants that will both be written to the shaders and material + // chunks (like user-defined spec constants). + void initPushConstants() noexcept; + // Return true if the shader is syntactically and semantically valid. // This method finds all the properties defined in the fragment and // vertex shaders of the material. @@ -829,6 +841,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { PropertyList mProperties; ParameterList mParameters; ConstantList mConstants; + PushConstantList mPushConstants; SubpassList mSubpasses; VariableList mVariables; OutputList mOutputs; diff --git a/package/ios/libs/filament/include/filament/Engine.h b/package/ios/libs/filament/include/filament/Engine.h index b741d3ab..2aed8273 100644 --- a/package/ios/libs/filament/include/filament/Engine.h +++ b/package/ios/libs/filament/include/filament/Engine.h @@ -315,7 +315,7 @@ class UTILS_PUBLIC Engine { * * @see View::setStereoscopicOptions */ - StereoscopicType stereoscopicType = StereoscopicType::INSTANCED; + StereoscopicType stereoscopicType = StereoscopicType::NONE; /* * The number of eyes to render when stereoscopic rendering is enabled. Supported values are @@ -340,6 +340,35 @@ class UTILS_PUBLIC Engine { * Disable backend handles use-after-free checks. */ bool disableHandleUseAfterFreeCheck = false; + + /* + * Sets a preferred shader language for Filament to use. + * + * The Metal backend supports two shader languages: MSL (Metal Shading Language) and + * METAL_LIBRARY (precompiled .metallib). This option controls which shader language is + * used when materials contain both. + * + * By default, when preferredShaderLanguage is unset, Filament will prefer METAL_LIBRARY + * shaders if present within a material, falling back to MSL. Setting + * preferredShaderLanguage to ShaderLanguage::MSL will instead instruct Filament to check + * for the presence of MSL in a material first, falling back to METAL_LIBRARY if MSL is not + * present. + * + * When using a non-Metal backend, setting this has no effect. + */ + enum class ShaderLanguage { + DEFAULT = 0, + MSL = 1, + METAL_LIBRARY = 2, + }; + ShaderLanguage preferredShaderLanguage = ShaderLanguage::DEFAULT; + + /* + * When the OpenGL ES backend is used, setting this value to true will force a GLES2.0 + * context if supported by the Platform, or if not, will have the backend pretend + * it's a GLES2 context. Ignored on other backends. + */ + bool forceGLES2Context = false; }; @@ -800,24 +829,51 @@ class UTILS_PUBLIC Engine { bool destroy(const InstanceBuffer* UTILS_NULLABLE p); //!< Destroys an InstanceBuffer object. void destroy(utils::Entity e); //!< Destroys all filament-known components from this entity - bool isValid(const BufferObject* UTILS_NULLABLE p); //!< Tells whether a BufferObject object is valid - bool isValid(const VertexBuffer* UTILS_NULLABLE p); //!< Tells whether an VertexBuffer object is valid - bool isValid(const Fence* UTILS_NULLABLE p); //!< Tells whether a Fence object is valid - bool isValid(const IndexBuffer* UTILS_NULLABLE p); //!< Tells whether an IndexBuffer object is valid - bool isValid(const SkinningBuffer* UTILS_NULLABLE p); //!< Tells whether a SkinningBuffer object is valid - bool isValid(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Tells whether a MorphTargetBuffer object is valid - bool isValid(const IndirectLight* UTILS_NULLABLE p); //!< Tells whether an IndirectLight object is valid - bool isValid(const Material* UTILS_NULLABLE p); //!< Tells whether an IndirectLight object is valid - bool isValid(const Renderer* UTILS_NULLABLE p); //!< Tells whether a Renderer object is valid - bool isValid(const Scene* UTILS_NULLABLE p); //!< Tells whether a Scene object is valid - bool isValid(const Skybox* UTILS_NULLABLE p); //!< Tells whether a SkyBox object is valid - bool isValid(const ColorGrading* UTILS_NULLABLE p); //!< Tells whether a ColorGrading object is valid - bool isValid(const SwapChain* UTILS_NULLABLE p); //!< Tells whether a SwapChain object is valid - bool isValid(const Stream* UTILS_NULLABLE p); //!< Tells whether a Stream object is valid - bool isValid(const Texture* UTILS_NULLABLE p); //!< Tells whether a Texture object is valid - bool isValid(const RenderTarget* UTILS_NULLABLE p); //!< Tells whether a RenderTarget object is valid - bool isValid(const View* UTILS_NULLABLE p); //!< Tells whether a View object is valid - bool isValid(const InstanceBuffer* UTILS_NULLABLE p); //!< Tells whether an InstanceBuffer object is valid + /** Tells whether a BufferObject object is valid */ + bool isValid(const BufferObject* UTILS_NULLABLE p) const; + /** Tells whether an VertexBuffer object is valid */ + bool isValid(const VertexBuffer* UTILS_NULLABLE p) const; + /** Tells whether a Fence object is valid */ + bool isValid(const Fence* UTILS_NULLABLE p) const; + /** Tells whether an IndexBuffer object is valid */ + bool isValid(const IndexBuffer* UTILS_NULLABLE p) const; + /** Tells whether a SkinningBuffer object is valid */ + bool isValid(const SkinningBuffer* UTILS_NULLABLE p) const; + /** Tells whether a MorphTargetBuffer object is valid */ + bool isValid(const MorphTargetBuffer* UTILS_NULLABLE p) const; + /** Tells whether an IndirectLight object is valid */ + bool isValid(const IndirectLight* UTILS_NULLABLE p) const; + /** Tells whether an Material object is valid */ + bool isValid(const Material* UTILS_NULLABLE p) const; + /** Tells whether an MaterialInstance object is valid. Use this if you already know + * which Material this MaterialInstance belongs to. DO NOT USE getMaterial(), this would + * defeat the purpose of validating the MaterialInstance. + */ + bool isValid(const Material* UTILS_NONNULL m, const MaterialInstance* UTILS_NULLABLE p) const; + /** Tells whether an MaterialInstance object is valid. Use this if the Material the + * MaterialInstance belongs to is not known. This method can be expensive. + */ + bool isValidExpensive(const MaterialInstance* UTILS_NULLABLE p) const; + /** Tells whether a Renderer object is valid */ + bool isValid(const Renderer* UTILS_NULLABLE p) const; + /** Tells whether a Scene object is valid */ + bool isValid(const Scene* UTILS_NULLABLE p) const; + /** Tells whether a SkyBox object is valid */ + bool isValid(const Skybox* UTILS_NULLABLE p) const; + /** Tells whether a ColorGrading object is valid */ + bool isValid(const ColorGrading* UTILS_NULLABLE p) const; + /** Tells whether a SwapChain object is valid */ + bool isValid(const SwapChain* UTILS_NULLABLE p) const; + /** Tells whether a Stream object is valid */ + bool isValid(const Stream* UTILS_NULLABLE p) const; + /** Tells whether a Texture object is valid */ + bool isValid(const Texture* UTILS_NULLABLE p) const; + /** Tells whether a RenderTarget object is valid */ + bool isValid(const RenderTarget* UTILS_NULLABLE p) const; + /** Tells whether a View object is valid */ + bool isValid(const View* UTILS_NULLABLE p) const; + /** Tells whether an InstanceBuffer object is valid */ + bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const; /** * Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until @@ -840,6 +896,15 @@ class UTILS_PUBLIC Engine { */ void flush(); + /** + * Get paused state of rendering thread. + * + *

Warning: This is an experimental API. + * + * @see setPaused + */ + bool isPaused() const noexcept; + /** * Pause or resume rendering thread. * @@ -865,6 +930,14 @@ class UTILS_PUBLIC Engine { */ void pumpMessageQueues(); + /** + * Switch the command queue to unprotected mode. Protected mode can be activated via + * Renderer::beginFrame() using a protected SwapChain. + * @see Renderer + * @see SwapChain + */ + void unprotected() noexcept; + /** * Returns the default Material. * diff --git a/package/ios/libs/filament/include/filament/MaterialChunkType.h b/package/ios/libs/filament/include/filament/MaterialChunkType.h index c80ac7d8..4a4561c1 100644 --- a/package/ios/libs/filament/include/filament/MaterialChunkType.h +++ b/package/ios/libs/filament/include/filament/MaterialChunkType.h @@ -53,6 +53,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialAttributeInfo = charTo64bitNum("MAT_ATTR"), MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialConstants = charTo64bitNum("MAT_CONS"), + MaterialPushConstants = charTo64bitNum("MAT_PCON"), MaterialName = charTo64bitNum("MAT_NAME"), MaterialVersion = charTo64bitNum("MAT_VERS"), diff --git a/package/ios/libs/filament/include/filament/MaterialEnums.h b/package/ios/libs/filament/include/filament/MaterialEnums.h index 9f348481..c15c6c45 100644 --- a/package/ios/libs/filament/include/filament/MaterialEnums.h +++ b/package/ios/libs/filament/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 51; +static constexpr size_t MATERIAL_VERSION = 53; /** * Supported shading models diff --git a/package/ios/libs/filament/include/filament/MaterialInstance.h b/package/ios/libs/filament/include/filament/MaterialInstance.h index 7252f9a4..2b8aaa9a 100644 --- a/package/ios/libs/filament/include/filament/MaterialInstance.h +++ b/package/ios/libs/filament/include/filament/MaterialInstance.h @@ -140,6 +140,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { * @param values Array of values to set to the named parameter array. * @param count Size of the array to set. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. + * @see Material::hasParameter */ template> void setParameter(const char* UTILS_NONNULL name, size_t nameLength, @@ -237,9 +238,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** * Gets the value of a parameter by name. * + * Note: Only supports non-texture parameters such as numeric and math types. + * * @param name Name of the parameter as defined by Material. Cannot be nullptr. * @param nameLength Length in `char` of the name parameter. * @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled. + * + * @see Material::hasParameter */ template T getParameter(const char* UTILS_NONNULL name, size_t nameLength) const; diff --git a/package/ios/libs/filament/include/filament/RenderableManager.h b/package/ios/libs/filament/include/filament/RenderableManager.h index bb50b7d1..363ef2d7 100644 --- a/package/ios/libs/filament/include/filament/RenderableManager.h +++ b/package/ios/libs/filament/include/filament/RenderableManager.h @@ -464,15 +464,30 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { Builder& boneIndicesAndWeights(size_t primitiveIndex, utils::FixedCapacityVector< utils::FixedCapacityVector> indicesAndWeightsVector) noexcept; + + /** + * Controls if the renderable has legacy vertex morphing targets, zero by default. This is + * required to enable GPU morphing. + * + * For legacy morphing, the attached VertexBuffer must provide data in the + * appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only + * supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must + * be enabled on the material definition: either via the legacyMorphing material attribute + * or by calling filamat::MaterialBuilder::useLegacyMorphing(). + * + * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis + * to advance the animation. + */ + Builder& morphing(size_t targetCount) noexcept; + /** * Controls if the renderable has vertex morphing targets, zero by default. This is * required to enable GPU morphing. * * Filament supports two morphing modes: standard (default) and legacy. * - * For standard morphing, A MorphTargetBuffer must be created and provided via - * RenderableManager::setMorphTargetBufferAt(). Standard morphing supports up to - * \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets. + * For standard morphing, A MorphTargetBuffer must be provided. + * Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets. * * For legacy morphing, the attached VertexBuffer must provide data in the * appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only @@ -483,29 +498,35 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis * to advance the animation. */ - Builder& morphing(size_t targetCount) noexcept; + Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; /** - * Specifies the morph target buffer for a primitive. - * - * The morph target buffer must have an associated renderable and geometry. Two conditions - * must be met: - * 1. The number of morph targets in the buffer must equal the renderable's morph target - * count. - * 2. The vertex count of each morph target must equal the geometry's vertex count. + * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead + */ + Builder& morphing(uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, + size_t offset, size_t count) noexcept; + + /** + * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead + */ + inline Builder& morphing(uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { + return morphing(level, primitiveIndex, morphTargetBuffer, 0, + morphTargetBuffer->getVertexCount()); + } + + /** + * Specifies the the range of the MorphTargetBuffer to use with this primitive. * * @param level the level of detail (lod), only 0 can be specified * @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor - * @param morphTargetBuffer specifies the morph target buffer * @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices) * @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3) */ Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count) noexcept; - inline Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; /** * Sets the drawing order for blended primitives. The drawing order is either global or @@ -765,14 +786,19 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /** * Associates a MorphTargetBuffer to the given primitive. */ + void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + size_t offset, size_t count); + + /** @deprecated */ void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count); - /** - * Utility method to change a MorphTargetBuffer to the given primitive - */ + /** @deprecated */ inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer); + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { + setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, + morphTargetBuffer->getVertexCount()); + } /** * Get a MorphTargetBuffer to the given primitive or null if it doesn't exist. @@ -906,20 +932,6 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { ~RenderableManager() = default; }; -RenderableManager::Builder& RenderableManager::Builder::morphing( - uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { - return morphing(level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); -} - -void RenderableManager::setMorphTargetBufferAt( - Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { - setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); -} - template Box RenderableManager::computeAABB( VECTOR const* UTILS_NONNULL vertices, diff --git a/package/ios/libs/filament/include/filament/SwapChain.h b/package/ios/libs/filament/include/filament/SwapChain.h index 0af01afc..6917507a 100644 --- a/package/ios/libs/filament/include/filament/SwapChain.h +++ b/package/ios/libs/filament/include/filament/SwapChain.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -115,7 +116,7 @@ class Engine; * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * SDL_SysWMinfo wmi; * SDL_VERSION(&wmi.version); - * ASSERT_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi), "SDL version unsupported!"); + * FILAMENT_CHECK_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi)) << "SDL version unsupported!"; * HDC nativeWindow = (HDC) wmi.info.win.hdc; * * using namespace filament; @@ -264,13 +265,22 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * backend. * * A FrameScheduledCallback can be set on an individual SwapChain through - * SwapChain::setFrameScheduledCallback. If the callback is set, then the SwapChain will *not* - * automatically schedule itself for presentation. Instead, the application must call the - * PresentCallable passed to the FrameScheduledCallback. + * SwapChain::setFrameScheduledCallback. If the callback is set for a given frame, then the + * SwapChain will *not* automatically schedule itself for presentation. Instead, the application + * must call the PresentCallable passed to the FrameScheduledCallback. * - * There may be only one FrameScheduledCallback set per SwapChain. A call to - * SwapChain::setFrameScheduledCallback will overwrite any previous FrameScheduledCallbacks set - * on the same SwapChain. + * Each SwapChain can have only one FrameScheduledCallback set per frame. If + * setFrameScheduledCallback is called multiple times on the same SwapChain before + * Renderer::endFrame(), the most recent call effectively overwrites any previously set + * callback. This allows the callback to be updated as needed before the frame has finished + * encoding. + * + * The "last" callback set by setFrameScheduledCallback gets "latched" when Renderer::endFrame() + * is executed. At this point, the state of the callback is fixed and is the one used for the + * frame that was just encoded. Subsequent changes to the callback using + * setFrameScheduledCallback after endFrame() apply to the next frame. + * + * Use \c setFrameScheduledCallback() (with default arguments) to unset the callback. * * If your application delays the call to the PresentCallable by, for example, calling it on a * separate thread, you must ensure all PresentCallables have been called before shutting down @@ -278,28 +288,26 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean * up all memory related to frame presentation. * - * @param callback A callback, or nullptr to unset. - * @param user An optional pointer to user data passed to the callback function. + * @param handler Handler to dispatch the callback or nullptr for the default handler. + * @param callback Callback called when the frame is scheduled. * * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other * backends ignore the callback (which will never be called) and proceed normally. * - * @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread. - * + * @see CallbackHandler * @see PresentCallable */ - void setFrameScheduledCallback(FrameScheduledCallback UTILS_NULLABLE callback, - void* UTILS_NULLABLE user = nullptr); + void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, + FrameScheduledCallback&& callback = {}); /** - * Returns the SwapChain::FrameScheduledCallback that was previously set with - * SwapChain::setFrameScheduledCallback, or nullptr if one is not set. + * Returns whether or not this SwapChain currently has a FrameScheduledCallback set. * - * @return the previously-set FrameScheduledCallback, or nullptr + * @return true, if the last call to setFrameScheduledCallback set a callback * * @see SwapChain::setFrameCompletedCallback */ - UTILS_NULLABLE FrameScheduledCallback getFrameScheduledCallback() const noexcept; + bool isFrameScheduledCallbackSet() const noexcept; /** * FrameCompletedCallback is a callback function that notifies an application when a frame's diff --git a/package/ios/libs/filament/include/filament/View.h b/package/ios/libs/filament/include/filament/View.h index 3cdd527f..1a1ed96f 100644 --- a/package/ios/libs/filament/include/filament/View.h +++ b/package/ios/libs/filament/include/filament/View.h @@ -189,6 +189,13 @@ class UTILS_PUBLIC View : public FilamentAPI { */ void setCamera(Camera* UTILS_NONNULL camera) noexcept; + /** + * Returns whether a Camera is set. + * @return true if a camera is set. + * @see setCamera() + */ + bool hasCamera() const noexcept; + /** * Returns the Camera currently associated with this View. * @return A reference to the Camera associated to this View. diff --git a/package/ios/libs/filament/include/gltfio/AssetLoader.h b/package/ios/libs/filament/include/gltfio/AssetLoader.h index f516166a..bf650f69 100644 --- a/package/ios/libs/filament/include/gltfio/AssetLoader.h +++ b/package/ios/libs/filament/include/gltfio/AssetLoader.h @@ -38,6 +38,23 @@ namespace filament::gltfio { class NodeManager; +// Use this struct to enable mikktspace-based tangent-space computation. +/** + * \struct AssetConfigurationExtended AssetLoader.h gltfio/AssetLoader.h + * \brief extends struct AssetConfiguration + * Useful if client needs mikktspace tangent space computation. + * NOTE: Android, iOS, Web are not supported. And only disk-local glTF resources are supported. + */ +struct AssetConfigurationExtended { + //! Optional The same parameter as provided to \struct ResourceConfiguration ResourceLoader.h + //! gltfio/ResourceLoader.h + char const* gltfPath; + + //! Client can use this method to check if the extended implementation is supported on their + //! platform or not. + static bool isSupported(); +}; + /** * \struct AssetConfiguration AssetLoader.h gltfio/AssetLoader.h * \brief Construction parameters for AssetLoader. @@ -62,6 +79,10 @@ struct AssetConfiguration { //! Optional default node name for anonymous nodes char* defaultNodeName = nullptr; + + //! Optional to enable mikktspace tangents. Lifetime of struct only needs to be maintained for + // the duration of the constructor of AssetLoader. + AssetConfigurationExtended* ext = nullptr; }; /** diff --git a/package/ios/libs/filament/include/utils/FixedCapacityVector.h b/package/ios/libs/filament/include/utils/FixedCapacityVector.h index f3990124..1221e7cc 100644 --- a/package/ios/libs/filament/include/utils/FixedCapacityVector.h +++ b/package/ios/libs/filament/include/utils/FixedCapacityVector.h @@ -299,6 +299,16 @@ class UTILS_PUBLIC FixedCapacityVector { } } + UTILS_NOINLINE + void shrink_to_fit() { + if (size() < capacity()) { + FixedCapacityVector t(construct_with_capacity, size(), allocator()); + t.mSize = size(); + std::uninitialized_move(begin(), end(), t.begin()); + this->swap(t); + } + } + private: enum construct_with_capacity_tag{ construct_with_capacity }; @@ -318,9 +328,9 @@ class UTILS_PUBLIC FixedCapacityVector { iterator assertCapacityForSize(size_type s) { if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) { - ASSERT_PRECONDITION(capacity() >= s, - "capacity exceeded: requested size %lu, available capacity %lu.", - (unsigned long)s, (unsigned long)capacity()); + FILAMENT_CHECK_PRECONDITION(capacity() >= s) + << "capacity exceeded: requested size " << (unsigned long)s + << "u, available capacity " << (unsigned long)capacity() << "u."; } return end(); } diff --git a/package/ios/libs/filament/include/utils/Panic.h b/package/ios/libs/filament/include/utils/Panic.h index c658da4b..6e9ac3f4 100644 --- a/package/ios/libs/filament/include/utils/Panic.h +++ b/package/ios/libs/filament/include/utils/Panic.h @@ -17,14 +17,28 @@ #ifndef TNT_UTILS_PANIC_H #define TNT_UTILS_PANIC_H +#ifdef FILAMENT_PANIC_USES_ABSL +# if FILAMENT_PANIC_USES_ABSL +# include "absl/log/log.h" +# define FILAMENT_CHECK_PRECONDITION CHECK +# define FILAMENT_CHECK_POSTCONDITION CHECK +# define FILAMENT_CHECK_ARITHMETIC CHECK +# endif +#endif + #include #include +#include #include +#include #ifdef __EXCEPTIONS # define UTILS_EXCEPTIONS 1 #else +# ifdef UTILS_EXCEPTIONS +# error UTILS_EXCEPTIONS is already defined! +# endif #endif /** @@ -280,12 +294,24 @@ class UTILS_PUBLIC Panic { */ virtual const char* what() const noexcept = 0; + /** + * Get the type of the panic (e.g. "Precondition") + * @return a C string containing the type of panic + */ + virtual const char* getType() const noexcept = 0; + /** * Get the reason string for the panic * @return a C string containing the reason for the panic */ virtual const char* getReason() const noexcept = 0; + /** + * Get a version of the reason string that is guaranteed to be constructed from literal + * strings only; it will contain no runtime information. + */ + virtual const char* getReasonLiteral() const noexcept = 0; + /** * Get the function name where the panic was detected. On debug build the fully qualified * function name is returned; on release builds only the function name is. @@ -334,7 +360,9 @@ class UTILS_PUBLIC TPanic : public Panic { const char* what() const noexcept override; // Panic interface + const char* getType() const noexcept override; const char* getReason() const noexcept override; + const char* getReasonLiteral() const noexcept override; const char* getFunction() const noexcept override; const char* getFile() const noexcept override; int getLine() const noexcept override; @@ -348,13 +376,14 @@ class UTILS_PUBLIC TPanic : public Panic { * @param function the name of the function where the error was detected * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected + * @param literal a literal version of the error message * @param format printf style string describing the error * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() */ - static void panic(char const* function, char const* file, int line, const char* format, ...) - UTILS_NORETURN; + static void panic(char const* function, char const* file, int line, char const* literal, + const char* format, ...) UTILS_NORETURN; /** * Depending on the mode set, either throws an exception of type T with the given reason plus @@ -363,43 +392,41 @@ class UTILS_PUBLIC TPanic : public Panic { * @param function the name of the function where the error was detected * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected - * @param s std::string describing the error + * @param literal a literal version of the error message + * @param reason std::string describing the error * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() */ - static inline void panic(char const* function, char const* file, int line, const std::string& s) - UTILS_NORETURN { - panic(function, file, line, s.c_str()); - } + static inline void panic( + char const* function, char const* file, int line, char const* literal, + std::string reason) UTILS_NORETURN; protected: - /** - * Creates a Panic. - * @param reason a description of the cause of the error - */ - explicit TPanic(std::string reason); /** * Creates a Panic with extra information about the error-site. * @param function the name of the function where the error was detected * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected + * @param literal a literal version of the error message * @param reason a description of the cause of the error */ - TPanic(char const* function, char const* file, int line, std::string reason); + TPanic(char const* function, char const* file, int line, char const* literal, + std::string reason); ~TPanic() override; private: void buildMessage(); - CallStack m_callstack; - std::string m_reason; - char const* const m_function = nullptr; - char const* const m_file = nullptr; - const int m_line = -1; - mutable std::string m_msg; + char const* const mFile = nullptr; // file where the panic happened + char const* const mFunction = nullptr; // function where the panic happened + int const mLine = -1; // line where the panic happened + std::string mLiteral; // reason for the panic, built only from literals + std::string mReason; // reason for the panic + mutable std::string mWhat; // fully formatted reason + CallStack mCallstack; }; namespace details { @@ -421,6 +448,7 @@ class UTILS_PUBLIC PreconditionPanic : public TPanic { // e.g.: invalid arguments using TPanic::TPanic; friend class TPanic; + constexpr static auto type = "Precondition"; }; /** @@ -434,6 +462,7 @@ class UTILS_PUBLIC PostconditionPanic : public TPanic { // e.g.: dead-lock would occur, arithmetic errors using TPanic::TPanic; friend class TPanic; + constexpr static auto type = "Postcondition"; }; /** @@ -447,8 +476,74 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { // e.g.: underflow, overflow, internal computations errors using TPanic::TPanic; friend class TPanic; + constexpr static auto type = "Arithmetic"; +}; + +namespace details { + +struct Voidify final { + template + void operator&&(const T&) const&& {} +}; + +class UTILS_PUBLIC PanicStream { +public: + PanicStream( + char const* function, + char const* file, + int line, + char const* message) noexcept; + + ~PanicStream(); + + PanicStream& operator<<(short value) noexcept; + PanicStream& operator<<(unsigned short value) noexcept; + + PanicStream& operator<<(char value) noexcept; + PanicStream& operator<<(unsigned char value) noexcept; + + PanicStream& operator<<(int value) noexcept; + PanicStream& operator<<(unsigned int value) noexcept; + + PanicStream& operator<<(long value) noexcept; + PanicStream& operator<<(unsigned long value) noexcept; + + PanicStream& operator<<(long long value) noexcept; + PanicStream& operator<<(unsigned long long value) noexcept; + + PanicStream& operator<<(float value) noexcept; + PanicStream& operator<<(double value) noexcept; + PanicStream& operator<<(long double value) noexcept; + + PanicStream& operator<<(bool value) noexcept; + + PanicStream& operator<<(const void* value) noexcept; + + PanicStream& operator<<(const char* string) noexcept; + PanicStream& operator<<(const unsigned char* string) noexcept; + + PanicStream& operator<<(std::string const& s) noexcept; + PanicStream& operator<<(std::string_view const& s) noexcept; + +protected: + io::sstream mStream; + char const* mFunction; + char const* mFile; + int mLine; + char const* mLiteral; }; +template +class TPanicStream : public PanicStream { +public: + using PanicStream::PanicStream; + ~TPanicStream() noexcept(false) UTILS_NORETURN { + T::panic(mFunction, mFile, mLine, mLiteral, mStream.c_str()); + } +}; + +} // namespace details + // ----------------------------------------------------------------------------------------------- } // namespace utils @@ -460,37 +555,70 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { # define PANIC_FUNCTION __func__ #endif + +#define FILAMENT_CHECK_CONDITION_IMPL(cond) \ + switch (0) \ + case 0: \ + default: \ + UTILS_LIKELY(cond) ? (void)0 : ::utils::details::Voidify()&& + +#define FILAMENT_PANIC_IMPL(message, TYPE) \ + ::utils::details::TPanicStream<::utils::TYPE>(PANIC_FUNCTION, PANIC_FILE(__FILE__), __LINE__, message) + +#ifndef FILAMENT_CHECK_PRECONDITION +#define FILAMENT_CHECK_PRECONDITION(condition) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, PreconditionPanic) +#endif + +#ifndef FILAMENT_CHECK_POSTCONDITION +#define FILAMENT_CHECK_POSTCONDITION(condition) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, PostconditionPanic) +#endif + +#ifndef FILAMENT_CHECK_ARITHMETIC +#define FILAMENT_CHECK_ARITHMETIC(condition) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, ArithmeticPanic) +#endif + +#define PANIC_PRECONDITION_IMPL(cond, format, ...) \ + ::utils::PreconditionPanic::panic(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) + +#define PANIC_POSTCONDITION_IMPL(cond, format, ...) \ + ::utils::PostconditionPanic::panic(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) + +#define PANIC_ARITHMETIC_IMPL(cond, format, ...) \ + ::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) + +#define PANIC_LOG_IMPL(cond, format, ...) \ + ::utils::details::panicLog(PANIC_FUNCTION, \ + PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) + /** * PANIC_PRECONDITION is a macro that reports a PreconditionPanic * @param format printf-style string describing the error in more details */ -#define PANIC_PRECONDITION(format, ...) \ - ::utils::PreconditionPanic::panic(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_PRECONDITION(format, ...) PANIC_PRECONDITION_IMPL(format, format, ##__VA_ARGS__) /** * PANIC_POSTCONDITION is a macro that reports a PostconditionPanic * @param format printf-style string describing the error in more details */ -#define PANIC_POSTCONDITION(format, ...) \ - ::utils::PostconditionPanic::panic(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_POSTCONDITION(format, ...) PANIC_POSTCONDITION_IMPL(format, format, ##__VA_ARGS__) /** * PANIC_ARITHMETIC is a macro that reports a ArithmeticPanic * @param format printf-style string describing the error in more details */ -#define PANIC_ARITHMETIC(format, ...) \ - ::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_ARITHMETIC(format, ...) PANIC_ARITHMETIC_IMPL(format, format, ##__VA_ARGS__) /** * PANIC_LOG is a macro that logs a Panic, and continues as usual. * @param format printf-style string describing the error in more details */ -#define PANIC_LOG(format, ...) \ - ::utils::details::panicLog(PANIC_FUNCTION, \ - PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__) +#define PANIC_LOG(format, ...) PANIC_LOG_IMPL(format, format, ##__VA_ARGS__) /** * @ingroup errors @@ -501,14 +629,14 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * @param format printf-style string describing the error in more details */ #define ASSERT_PRECONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__) : (void)0) + (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif @@ -529,14 +657,14 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * @endcode */ #define ASSERT_POSTCONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__) : (void)0) + (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -557,14 +685,14 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * @endcode */ #define ASSERT_ARITHMETIC(cond, format, ...) \ - (!(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__) : (void)0) + (!(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true) + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -588,6 +716,7 @@ class UTILS_PUBLIC ArithmeticPanic : public TPanic { * } * @endcode */ -#define ASSERT_DESTRUCTOR(cond, format, ...) (!(cond) ? PANIC_LOG(format, ##__VA_ARGS__) : (void)0) +#define ASSERT_DESTRUCTOR(cond, format, ...) \ + (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #endif // TNT_UTILS_PANIC_H diff --git a/package/ios/libs/filament/include/utils/StructureOfArrays.h b/package/ios/libs/filament/include/utils/StructureOfArrays.h index a4309584..c0b2315e 100644 --- a/package/ios/libs/filament/include/utils/StructureOfArrays.h +++ b/package/ios/libs/filament/include/utils/StructureOfArrays.h @@ -368,7 +368,7 @@ class StructureOfArraysBase { size_t last = mSize++; // Fold expression on the comma operator ([&]{ - new(std::get(mArrays) + last) Elements{std::get(args)}; + new(std::get(mArrays) + last) Elements{std::get(std::forward(args))}; }() , ...); } @@ -513,7 +513,7 @@ class StructureOfArraysBase { return (soa.elementAt(i) = other); } UTILS_ALWAYS_INLINE Type const& operator = (Type&& other) noexcept { - return (soa.elementAt(i) = other); + return (soa.elementAt(i) = std::forward(other)); } // comparisons UTILS_ALWAYS_INLINE bool operator==(Type const& other) const { diff --git a/package/ios/libs/filament/include/utils/algorithm.h b/package/ios/libs/filament/include/utils/algorithm.h index ea5ca44f..7a747b84 100644 --- a/package/ios/libs/filament/include/utils/algorithm.h +++ b/package/ios/libs/filament/include/utils/algorithm.h @@ -22,6 +22,7 @@ #include // for std::enable_if #include +#include #include namespace utils { @@ -43,9 +44,15 @@ constexpr inline T clz(T x) noexcept { static_assert(sizeof(T) * CHAR_BIT <= 128, "details::clz() only support up to 128 bits"); x |= (x >> 1u); x |= (x >> 2u); - x |= (x >> 4u); - x |= (x >> 8u); - x |= (x >> 16u); + if constexpr (sizeof(T) * CHAR_BIT >= 8) { // just to silence compiler warning + x |= (x >> 4u); + } + if constexpr (sizeof(T) * CHAR_BIT >= 16) { // just to silence compiler warning + x |= (x >> 8u); + } + if constexpr (sizeof(T) * CHAR_BIT >= 32) { // just to silence compiler warning + x |= (x >> 16u); + } if constexpr (sizeof(T) * CHAR_BIT >= 64) { // just to silence compiler warning x |= (x >> 32u); } @@ -67,11 +74,15 @@ constexpr inline T ctz(T x) noexcept { x &= -x; #endif if (x) c--; - if (sizeof(T) * CHAR_BIT >= 64) { + if constexpr (sizeof(T) * CHAR_BIT >= 64) { if (x & T(0x00000000FFFFFFFF)) c -= 32; } - if (x & T(0x0000FFFF0000FFFF)) c -= 16; - if (x & T(0x00FF00FF00FF00FF)) c -= 8; + if constexpr (sizeof(T) * CHAR_BIT >= 32) { + if (x & T(0x0000FFFF0000FFFF)) c -= 16; + } + if constexpr (sizeof(T) * CHAR_BIT >= 16) { + if (x & T(0x00FF00FF00FF00FF)) c -= 8; + } if (x & T(0x0F0F0F0F0F0F0F0F)) c -= 4; if (x & T(0x3333333333333333)) c -= 2; if (x & T(0x5555555555555555)) c -= 1; @@ -80,6 +91,24 @@ constexpr inline T ctz(T x) noexcept { } // namespace details +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE clz(unsigned char x) noexcept { +#if __has_builtin(__builtin_clz) + return __builtin_clz((unsigned int)x) - 24; +#else + return details::clz(x); +#endif +} + +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE clz(unsigned short x) noexcept { +#if __has_builtin(__builtin_clz) + return __builtin_clz((unsigned int)x) - 16; +#else + return details::clz(x); +#endif +} + constexpr inline UTILS_PUBLIC UTILS_PURE unsigned int UTILS_ALWAYS_INLINE clz(unsigned int x) noexcept { #if __has_builtin(__builtin_clz) @@ -107,6 +136,24 @@ unsigned long long UTILS_ALWAYS_INLINE clz(unsigned long long x) noexcept { #endif } +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE ctz(unsigned char x) noexcept { +#if __has_builtin(__builtin_ctz) + return __builtin_ctz(x); +#else + return details::ctz(x); +#endif +} + +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE ctz(unsigned short x) noexcept { +#if __has_builtin(__builtin_ctz) + return __builtin_ctz(x); +#else + return details::ctz(x); +#endif +} + constexpr inline UTILS_PUBLIC UTILS_PURE unsigned int UTILS_ALWAYS_INLINE ctz(unsigned int x) noexcept { #if __has_builtin(__builtin_ctz) @@ -134,6 +181,24 @@ unsigned long long UTILS_ALWAYS_INLINE ctz(unsigned long long x) noexcept { #endif } +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE popcount(unsigned char x) noexcept { +#if __has_builtin(__builtin_popcount) + return __builtin_popcount(x); +#else + return details::popcount(x); +#endif +} + +constexpr inline UTILS_PUBLIC UTILS_PURE +unsigned int UTILS_ALWAYS_INLINE popcount(unsigned short x) noexcept { +#if __has_builtin(__builtin_popcount) + return __builtin_popcount(x); +#else + return details::popcount(x); +#endif +} + constexpr inline UTILS_PUBLIC UTILS_PURE unsigned int UTILS_ALWAYS_INLINE popcount(unsigned int x) noexcept { #if __has_builtin(__builtin_popcount) @@ -161,11 +226,6 @@ unsigned long long UTILS_ALWAYS_INLINE popcount(unsigned long long x) noexcept { #endif } -constexpr inline UTILS_PUBLIC UTILS_PURE -uint8_t UTILS_ALWAYS_INLINE popcount(uint8_t x) noexcept { - return (uint8_t)popcount((unsigned int)x); -} - template::value && std::is_unsigned::value>> constexpr inline UTILS_PUBLIC UTILS_PURE diff --git a/package/ios/libs/filament/include/utils/bitset.h b/package/ios/libs/filament/include/utils/bitset.h index 281e5dfc..8844fdb8 100644 --- a/package/ios/libs/filament/include/utils/bitset.h +++ b/package/ios/libs/filament/include/utils/bitset.h @@ -60,6 +60,11 @@ class UTILS_PUBLIC bitset { std::fill(std::begin(storage), std::end(storage), 0); } + template> + explicit bitset(U value) noexcept { + storage[0] = value; + } + T getBitsAt(size_t n) const noexcept { assert_invariant(n +#include + +#include + +namespace utils::io { + +class UTILS_PUBLIC sstream : public ostream { +public: + ostream& flush() noexcept override; + const char* c_str() const noexcept; + size_t length() const noexcept; +}; + +} // namespace utils::io + +#endif // TNT_UTILS_SSTREAM_H From 26dff9abb1fe0546ddc818564032cc6691420fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 10 Jan 2026 12:32:42 +0100 Subject: [PATCH 6/9] wip: update filament to latest main commit --- bullet3 | 2 +- filament | 2 +- package/android/CMakeLists.txt | 2 + package/android/libs/filament/README.md | 15 +- .../filament/include/backend/AcquiredImage.h | 1 + .../include/backend/BufferDescriptor.h | 23 +- .../include/backend/CallbackHandler.h | 2 +- .../backend/DescriptorSetOffsetArray.h | 109 +++ .../filament/include/backend/DriverEnums.h | 629 ++++++++++-- .../libs/filament/include/backend/Handle.h | 58 +- .../filament/include/backend/PipelineState.h | 12 +- .../include/backend/PixelBufferDescriptor.h | 46 +- .../libs/filament/include/backend/Platform.h | 389 +++++++- .../include/backend/PresentCallable.h | 18 +- .../libs/filament/include/backend/Program.h | 153 +-- .../include/backend/TargetBufferInfo.h | 33 +- .../include/backend/platforms/AndroidNdk.h | 79 ++ .../backend/platforms/OpenGLPlatform.h | 82 +- .../backend/platforms/PlatformCocoaGL.h | 13 +- .../backend/platforms/PlatformCocoaTouchGL.h | 12 +- .../include/backend/platforms/PlatformEGL.h | 79 +- .../backend/platforms/PlatformEGLAndroid.h | 104 +- .../backend/platforms/PlatformEGLHeadless.h | 2 +- .../include/backend/platforms/PlatformGLX.h | 21 +- .../backend/platforms/PlatformMetal-ObjC.h | 38 + .../include/backend/platforms/PlatformMetal.h | 118 +++ .../backend/platforms/PlatformOSMesa.h | 69 ++ .../include/backend/platforms/PlatformWGL.h | 10 +- .../include/backend/platforms/PlatformWebGL.h | 7 +- .../backend/platforms/VulkanPlatform.h | 269 ++++- .../backend/platforms/VulkanPlatformAndroid.h | 111 +++ .../backend/platforms/VulkanPlatformApple.h | 33 + .../backend/platforms/VulkanPlatformLinux.h | 33 + .../backend/platforms/VulkanPlatformWindows.h | 33 + .../backend/platforms/WebGPUPlatform.h | 87 ++ .../backend/platforms/WebGPUPlatformAndroid.h | 35 + .../backend/platforms/WebGPUPlatformApple.h | 35 + .../backend/platforms/WebGPUPlatformLinux.h | 35 + .../backend/platforms/WebGPUPlatformWindows.h | 35 + .../filament/include/camutils/Manipulator.h | 3 + .../libs/filament/include/filamat/Enums.h | 2 + .../include/filamat/IncludeCallback.h | 71 -- .../include/filamat/MaterialBuilder.h | 194 +++- .../libs/filament/include/filamat/Package.h | 1 + .../generatePrefilterMipmap.h | 94 ++ .../IBLPrefilterContext.h | 2 - .../libs/filament/include/filament/Box.h | 15 +- .../filament/include/filament/BufferObject.h | 34 +- .../libs/filament/include/filament/Camera.h | 30 +- .../libs/filament/include/filament/Color.h | 6 +- .../filament/include/filament/ColorGrading.h | 12 +- .../libs/filament/include/filament/Engine.h | 283 +++++- .../filament/include/filament/FilamentAPI.h | 33 + .../filament/include/filament/IndexBuffer.h | 98 +- .../filament/include/filament/IndirectLight.h | 23 +- .../include/filament/InstanceBuffer.h | 41 +- .../libs/filament/include/filament/Material.h | 74 +- .../include/filament/MaterialChunkType.h | 11 +- .../filament/include/filament/MaterialEnums.h | 20 +- .../include/filament/MaterialInstance.h | 101 +- .../include/filament/MorphTargetBuffer.h | 30 +- .../libs/filament/include/filament/Options.h | 83 +- .../filament/include/filament/RenderTarget.h | 32 +- .../include/filament/RenderableManager.h | 104 +- .../libs/filament/include/filament/Renderer.h | 105 +- .../libs/filament/include/filament/Scene.h | 5 + .../include/filament/SkinningBuffer.h | 30 +- .../libs/filament/include/filament/Skybox.h | 14 + .../libs/filament/include/filament/Stream.h | 38 +- .../filament/include/filament/SwapChain.h | 123 ++- .../libs/filament/include/filament/Sync.h | 53 + .../libs/filament/include/filament/Texture.h | 278 ++++-- .../include/filament/TextureSampler.h | 4 +- .../filament/include/filament/ToneMapper.h | 31 + .../filament/include/filament/VertexBuffer.h | 131 ++- .../libs/filament/include/filament/View.h | 106 +- .../include/geometry/TangentSpaceMesh.h | 2 +- .../include/gltfio/MaterialProvider.h | 17 +- .../filament/include/gltfio/TextureProvider.h | 13 + .../include/gltfio/materials/uberarchive.h | 5 +- .../libs/filament/include/ibl/CubemapIBL.h | 2 +- .../libs/filament/include/math/TQuatHelpers.h | 5 +- .../libs/filament/include/math/compiler.h | 5 + .../android/libs/filament/include/math/fast.h | 2 +- .../android/libs/filament/include/math/half.h | 14 +- .../android/libs/filament/include/math/mat2.h | 2 +- .../android/libs/filament/include/math/mat3.h | 4 +- .../android/libs/filament/include/math/mat4.h | 4 +- .../private/backend/VirtualMachineEnv.h | 64 ++ .../filament/include/uberz/WritableArchive.h | 2 +- .../libs/filament/include/utils/Allocator.h | 102 +- .../libs/filament/include/utils/BitmaskEnum.h | 30 +- .../libs/filament/include/utils/CString.h | 415 +++++++- .../libs/filament/include/utils/CallStack.h | 22 +- .../include/utils/FixedCapacityVector.h | 72 +- .../libs/filament/include/utils/Hash.h | 149 +++ .../filament/include/utils/ImmutableCString.h | 199 ++++ .../libs/filament/include/utils/InternPool.h | 146 +++ .../libs/filament/include/utils/Invocable.h | 34 +- .../libs/filament/include/utils/Logger.h | 126 +++ .../filament/include/utils/MonotonicRingMap.h | 157 +++ .../include/utils/NameComponentManager.h | 4 +- .../libs/filament/include/utils/Panic.h | 100 +- .../filament/include/utils/RefCountedMap.h | 248 +++++ .../utils/SingleInstanceComponentManager.h | 6 +- .../libs/filament/include/utils/Slice.h | 142 +-- .../filament/include/utils/StaticString.h | 115 +++ .../libs/filament/include/utils/Status.h | 162 +++ .../include/utils/StructureOfArrays.h | 40 +- .../libs/filament/include/utils/Systrace.h | 8 +- .../libs/filament/include/utils/algorithm.h | 6 +- .../libs/filament/include/utils/bitset.h | 31 +- .../libs/filament/include/utils/compiler.h | 17 +- .../libs/filament/include/utils/debug.h | 2 +- .../libs/filament/include/utils/memalign.h | 8 +- .../libs/filament/include/utils/ostream.h | 29 +- .../include/viewer/AutomationEngine.h | 24 + .../libs/filament/include/viewer/Settings.h | 8 + package/android/src/main/cpp/RNFFilament.cpp | 3 + package/cpp/core/RNFEngineConfigHelper.cpp | 4 +- package/ios/libs/filament/README.md | 15 +- .../filament/include/backend/AcquiredImage.h | 1 + .../include/backend/BufferDescriptor.h | 23 +- .../include/backend/CallbackHandler.h | 2 +- .../backend/DescriptorSetOffsetArray.h | 109 +++ .../filament/include/backend/DriverEnums.h | 629 ++++++++++-- .../libs/filament/include/backend/Handle.h | 58 +- .../filament/include/backend/PipelineState.h | 12 +- .../include/backend/PixelBufferDescriptor.h | 46 +- .../libs/filament/include/backend/Platform.h | 389 +++++++- .../include/backend/PresentCallable.h | 18 +- .../libs/filament/include/backend/Program.h | 153 +-- .../include/backend/TargetBufferInfo.h | 33 +- .../include/backend/platforms/AndroidNdk.h | 79 ++ .../backend/platforms/OpenGLPlatform.h | 82 +- .../backend/platforms/PlatformCocoaGL.h | 13 +- .../backend/platforms/PlatformCocoaTouchGL.h | 12 +- .../include/backend/platforms/PlatformEGL.h | 79 +- .../backend/platforms/PlatformEGLAndroid.h | 104 +- .../backend/platforms/PlatformEGLHeadless.h | 2 +- .../include/backend/platforms/PlatformGLX.h | 21 +- .../backend/platforms/PlatformMetal-ObjC.h | 38 + .../include/backend/platforms/PlatformMetal.h | 118 +++ .../backend/platforms/PlatformOSMesa.h | 69 ++ .../include/backend/platforms/PlatformWGL.h | 10 +- .../include/backend/platforms/PlatformWebGL.h | 7 +- .../backend/platforms/VulkanPlatform.h | 269 ++++- .../backend/platforms/VulkanPlatformAndroid.h | 111 +++ .../backend/platforms/VulkanPlatformApple.h | 33 + .../backend/platforms/VulkanPlatformLinux.h | 33 + .../backend/platforms/VulkanPlatformWindows.h | 33 + .../backend/platforms/WebGPUPlatform.h | 87 ++ .../backend/platforms/WebGPUPlatformAndroid.h | 35 + .../backend/platforms/WebGPUPlatformApple.h | 35 + .../backend/platforms/WebGPUPlatformLinux.h | 35 + .../backend/platforms/WebGPUPlatformWindows.h | 35 + .../filament/include/camutils/Manipulator.h | 3 + .../ios/libs/filament/include/filamat/Enums.h | 98 -- .../include/filamat/IncludeCallback.h | 71 -- .../include/filamat/MaterialBuilder.h | 922 ------------------ .../libs/filament/include/filamat/Package.h | 103 -- .../generatePrefilterMipmap.h | 94 ++ .../IBLPrefilterContext.h | 2 - .../ios/libs/filament/include/filament/Box.h | 15 +- .../filament/include/filament/BufferObject.h | 34 +- .../libs/filament/include/filament/Camera.h | 30 +- .../libs/filament/include/filament/Color.h | 6 +- .../filament/include/filament/ColorGrading.h | 12 +- .../libs/filament/include/filament/Engine.h | 283 +++++- .../filament/include/filament/FilamentAPI.h | 33 + .../filament/include/filament/IndexBuffer.h | 98 +- .../filament/include/filament/IndirectLight.h | 23 +- .../include/filament/InstanceBuffer.h | 41 +- .../libs/filament/include/filament/Material.h | 74 +- .../include/filament/MaterialChunkType.h | 11 +- .../filament/include/filament/MaterialEnums.h | 20 +- .../include/filament/MaterialInstance.h | 101 +- .../include/filament/MorphTargetBuffer.h | 30 +- .../libs/filament/include/filament/Options.h | 83 +- .../filament/include/filament/RenderTarget.h | 32 +- .../include/filament/RenderableManager.h | 104 +- .../libs/filament/include/filament/Renderer.h | 105 +- .../libs/filament/include/filament/Scene.h | 5 + .../include/filament/SkinningBuffer.h | 30 +- .../libs/filament/include/filament/Skybox.h | 14 + .../libs/filament/include/filament/Stream.h | 38 +- .../filament/include/filament/SwapChain.h | 123 ++- .../ios/libs/filament/include/filament/Sync.h | 53 + .../libs/filament/include/filament/Texture.h | 278 ++++-- .../include/filament/TextureSampler.h | 4 +- .../filament/include/filament/ToneMapper.h | 31 + .../filament/include/filament/VertexBuffer.h | 131 ++- .../ios/libs/filament/include/filament/View.h | 106 +- .../include/geometry/TangentSpaceMesh.h | 2 +- .../include/gltfio/MaterialProvider.h | 17 +- .../filament/include/gltfio/TextureProvider.h | 13 + .../include/gltfio/materials/uberarchive.h | 5 +- .../libs/filament/include/ibl/CubemapIBL.h | 2 +- .../libs/filament/include/math/TQuatHelpers.h | 5 +- .../ios/libs/filament/include/math/compiler.h | 5 + package/ios/libs/filament/include/math/fast.h | 2 +- package/ios/libs/filament/include/math/half.h | 14 +- package/ios/libs/filament/include/math/mat2.h | 2 +- package/ios/libs/filament/include/math/mat3.h | 4 +- package/ios/libs/filament/include/math/mat4.h | 4 +- .../private/backend/VirtualMachineEnv.h | 64 ++ .../filament/include/uberz/WritableArchive.h | 2 +- .../libs/filament/include/utils/Allocator.h | 102 +- .../libs/filament/include/utils/BitmaskEnum.h | 30 +- .../ios/libs/filament/include/utils/CString.h | 415 +++++++- .../libs/filament/include/utils/CallStack.h | 22 +- .../include/utils/FixedCapacityVector.h | 72 +- .../ios/libs/filament/include/utils/Hash.h | 149 +++ .../filament/include/utils/ImmutableCString.h | 199 ++++ .../libs/filament/include/utils/InternPool.h | 146 +++ .../libs/filament/include/utils/Invocable.h | 34 +- .../ios/libs/filament/include/utils/Logger.h | 126 +++ .../filament/include/utils/MonotonicRingMap.h | 157 +++ .../include/utils/NameComponentManager.h | 4 +- .../ios/libs/filament/include/utils/Panic.h | 100 +- .../filament/include/utils/RefCountedMap.h | 248 +++++ .../utils/SingleInstanceComponentManager.h | 6 +- .../ios/libs/filament/include/utils/Slice.h | 142 +-- .../filament/include/utils/StaticString.h | 115 +++ .../ios/libs/filament/include/utils/Status.h | 162 +++ .../include/utils/StructureOfArrays.h | 40 +- .../libs/filament/include/utils/Systrace.h | 8 +- .../libs/filament/include/utils/algorithm.h | 6 +- .../ios/libs/filament/include/utils/bitset.h | 31 +- .../libs/filament/include/utils/compiler.h | 17 +- .../ios/libs/filament/include/utils/debug.h | 2 +- .../libs/filament/include/utils/memalign.h | 8 +- .../ios/libs/filament/include/utils/ostream.h | 29 +- .../include/viewer/AutomationEngine.h | 24 + .../libs/filament/include/viewer/Settings.h | 8 + package/scripts/build-filament.sh | 10 + 236 files changed, 13147 insertions(+), 3174 deletions(-) create mode 100644 package/android/libs/filament/include/backend/DescriptorSetOffsetArray.h create mode 100644 package/android/libs/filament/include/backend/platforms/AndroidNdk.h create mode 100644 package/android/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h create mode 100644 package/android/libs/filament/include/backend/platforms/PlatformMetal.h create mode 100644 package/android/libs/filament/include/backend/platforms/PlatformOSMesa.h create mode 100644 package/android/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h create mode 100644 package/android/libs/filament/include/backend/platforms/VulkanPlatformApple.h create mode 100644 package/android/libs/filament/include/backend/platforms/VulkanPlatformLinux.h create mode 100644 package/android/libs/filament/include/backend/platforms/VulkanPlatformWindows.h create mode 100644 package/android/libs/filament/include/backend/platforms/WebGPUPlatform.h create mode 100644 package/android/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h create mode 100644 package/android/libs/filament/include/backend/platforms/WebGPUPlatformApple.h create mode 100644 package/android/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h create mode 100644 package/android/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h delete mode 100644 package/android/libs/filament/include/filamat/IncludeCallback.h create mode 100644 package/android/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h create mode 100644 package/android/libs/filament/include/filament/Sync.h create mode 100644 package/android/libs/filament/include/private/backend/VirtualMachineEnv.h create mode 100644 package/android/libs/filament/include/utils/Hash.h create mode 100644 package/android/libs/filament/include/utils/ImmutableCString.h create mode 100644 package/android/libs/filament/include/utils/InternPool.h create mode 100644 package/android/libs/filament/include/utils/Logger.h create mode 100644 package/android/libs/filament/include/utils/MonotonicRingMap.h create mode 100644 package/android/libs/filament/include/utils/RefCountedMap.h create mode 100644 package/android/libs/filament/include/utils/StaticString.h create mode 100644 package/android/libs/filament/include/utils/Status.h create mode 100644 package/ios/libs/filament/include/backend/DescriptorSetOffsetArray.h create mode 100644 package/ios/libs/filament/include/backend/platforms/AndroidNdk.h create mode 100644 package/ios/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h create mode 100644 package/ios/libs/filament/include/backend/platforms/PlatformMetal.h create mode 100644 package/ios/libs/filament/include/backend/platforms/PlatformOSMesa.h create mode 100644 package/ios/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h create mode 100644 package/ios/libs/filament/include/backend/platforms/VulkanPlatformApple.h create mode 100644 package/ios/libs/filament/include/backend/platforms/VulkanPlatformLinux.h create mode 100644 package/ios/libs/filament/include/backend/platforms/VulkanPlatformWindows.h create mode 100644 package/ios/libs/filament/include/backend/platforms/WebGPUPlatform.h create mode 100644 package/ios/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h create mode 100644 package/ios/libs/filament/include/backend/platforms/WebGPUPlatformApple.h create mode 100644 package/ios/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h create mode 100644 package/ios/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h delete mode 100644 package/ios/libs/filament/include/filamat/Enums.h delete mode 100644 package/ios/libs/filament/include/filamat/IncludeCallback.h delete mode 100644 package/ios/libs/filament/include/filamat/MaterialBuilder.h delete mode 100644 package/ios/libs/filament/include/filamat/Package.h create mode 100644 package/ios/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h create mode 100644 package/ios/libs/filament/include/filament/Sync.h create mode 100644 package/ios/libs/filament/include/private/backend/VirtualMachineEnv.h create mode 100644 package/ios/libs/filament/include/utils/Hash.h create mode 100644 package/ios/libs/filament/include/utils/ImmutableCString.h create mode 100644 package/ios/libs/filament/include/utils/InternPool.h create mode 100644 package/ios/libs/filament/include/utils/Logger.h create mode 100644 package/ios/libs/filament/include/utils/MonotonicRingMap.h create mode 100644 package/ios/libs/filament/include/utils/RefCountedMap.h create mode 100644 package/ios/libs/filament/include/utils/StaticString.h create mode 100644 package/ios/libs/filament/include/utils/Status.h diff --git a/bullet3 b/bullet3 index e9c461b0..6bb8d112 160000 --- a/bullet3 +++ b/bullet3 @@ -1 +1 @@ -Subproject commit e9c461b0ace140d5c73972760781d94b7b5eee53 +Subproject commit 6bb8d1123d8a55d407b19fd3357c724d0f5c9d3c diff --git a/filament b/filament index cab799f5..85ebd67a 160000 --- a/filament +++ b/filament @@ -1 +1 @@ -Subproject commit cab799f531ce2fe871083b201efa2c142ed0bbb5 +Subproject commit 85ebd67a282433bde6a071f72bc048d94ba031d4 diff --git a/package/android/CMakeLists.txt b/package/android/CMakeLists.txt index d39d619c..a7e1ec71 100644 --- a/package/android/CMakeLists.txt +++ b/package/android/CMakeLists.txt @@ -112,6 +112,8 @@ target_include_directories( "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper "${FILAMENT_DIR}/include" "${BULLET3_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../../filament/filament/backend/include" # <-- Filament backend private headers (dev) + "${FILAMENT_DIR}/include/private" # <-- Filament private headers (distributed package) ) # Link everything together diff --git a/package/android/libs/filament/README.md b/package/android/libs/filament/README.md index 79ec596b..abe815eb 100644 --- a/package/android/libs/filament/README.md +++ b/package/android/libs/filament/README.md @@ -92,14 +92,14 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory. ### Linux ```make -FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl +FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil CC=clang++ main: main.o $(CC) -Llib/x86_64/ main.o $(FILAMENT_LIBS) -lpthread -lc++ -ldl -o main main.o: main.cpp - $(CC) -Iinclude/ -std=c++17 -pthread -c main.cpp + $(CC) -Iinclude/ -std=c++20 -pthread -c main.cpp clean: rm -f main main.o @@ -110,15 +110,16 @@ clean: ### macOS ```make -FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl +FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil FRAMEWORKS=-framework Cocoa -framework Metal -framework CoreVideo CC=clang++ +ARCH ?= $(shell uname -m) main: main.o - $(CC) -Llib/x86_64/ main.o $(FILAMENT_LIBS) $(FRAMEWORKS) -o main + $(CC) -Llib/$(ARCH)/ main.o $(FILAMENT_LIBS) $(FRAMEWORKS) -o main main.o: main.cpp - $(CC) -Iinclude/ -std=c++17 -c main.cpp + $(CC) -Iinclude/ -std=c++20 -c main.cpp clean: rm -f main main.o @@ -139,7 +140,7 @@ used to change the run-time library version. ```make FILAMENT_LIBS=filament.lib backend.lib bluegl.lib bluevk.lib filabridge.lib filaflat.lib \ - utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib + utils.lib geometry.lib smol-v.lib ibl.lib abseil.lib CC=cl.exe main.exe: main.obj @@ -147,7 +148,7 @@ main.exe: main.obj gdi32.lib user32.lib opengl32.lib main.obj: main.cpp - $(CC) /MT /Iinclude\\ /std:c++17 /c main.cpp + $(CC) /MT /Iinclude\\ /std:c++20 /c main.cpp clean: del main.exe main.obj diff --git a/package/android/libs/filament/include/backend/AcquiredImage.h b/package/android/libs/filament/include/backend/AcquiredImage.h index fec27a53..ff0840e5 100644 --- a/package/android/libs/filament/include/backend/AcquiredImage.h +++ b/package/android/libs/filament/include/backend/AcquiredImage.h @@ -18,6 +18,7 @@ #define TNT_FILAMENT_BACKEND_PRIVATE_ACQUIREDIMAGE_H #include +#include namespace filament::backend { diff --git a/package/android/libs/filament/include/backend/BufferDescriptor.h b/package/android/libs/filament/include/backend/BufferDescriptor.h index ebb57537..afc0da20 100644 --- a/package/android/libs/filament/include/backend/BufferDescriptor.h +++ b/package/android/libs/filament/include/backend/BufferDescriptor.h @@ -20,10 +20,15 @@ #define TNT_FILAMENT_BACKEND_BUFFERDESCRIPTOR_H #include -#include + +#include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { class CallbackHandler; @@ -89,8 +94,8 @@ class UTILS_PUBLIC BufferDescriptor { * @param callback A callback used to release the CPU buffer from this BufferDescriptor * @param user An opaque user pointer passed to the callback function when it's called */ - BufferDescriptor(void const* buffer, size_t size, - Callback callback = nullptr, void* user = nullptr) noexcept + BufferDescriptor(void const* buffer, size_t const size, + Callback const callback = nullptr, void* user = nullptr) noexcept : buffer(const_cast(buffer)), size(size), mCallback(callback), mUser(user) { } @@ -98,11 +103,12 @@ class UTILS_PUBLIC BufferDescriptor { * Creates a BufferDescriptor that references a CPU memory-buffer * @param buffer Memory address of the CPU buffer to reference * @param size Size of the CPU buffer in bytes + * @param handler A custom handler for the callback * @param callback A callback used to release the CPU buffer from this BufferDescriptor * @param user An opaque user pointer passed to the callback function when it's called */ - BufferDescriptor(void const* buffer, size_t size, - CallbackHandler* handler, Callback callback, void* user = nullptr) noexcept + BufferDescriptor(void const* buffer, size_t const size, + CallbackHandler* handler, Callback const callback, void* user = nullptr) noexcept : buffer(const_cast(buffer)), size(size), mCallback(callback), mUser(user), mHandler(handler) { } @@ -116,8 +122,9 @@ class UTILS_PUBLIC BufferDescriptor { * * @param buffer Memory address of the CPU buffer to reference * @param size Size of the CPU buffer in bytes + * @param data A pointer to the data * @param handler Handler to use to dispatch the callback, or nullptr for the default handler - * @return a new BufferDescriptor + * @return A new BufferDescriptor */ template static BufferDescriptor make(void const* buffer, size_t size, T* data, @@ -164,7 +171,7 @@ class UTILS_PUBLIC BufferDescriptor { * @param callback The new callback function * @param user An opaque user pointer passed to the callbeck function when it's called */ - void setCallback(Callback callback, void* user = nullptr) noexcept { + void setCallback(Callback const callback, void* user = nullptr) noexcept { this->mCallback = callback; this->mUser = user; this->mHandler = nullptr; @@ -176,7 +183,7 @@ class UTILS_PUBLIC BufferDescriptor { * @param callback The new callback function * @param user An opaque user pointer passed to the callbeck function when it's called */ - void setCallback(CallbackHandler* handler, Callback callback, void* user = nullptr) noexcept { + void setCallback(CallbackHandler* handler, Callback const callback, void* user = nullptr) noexcept { mCallback = callback; mUser = user; mHandler = handler; diff --git a/package/android/libs/filament/include/backend/CallbackHandler.h b/package/android/libs/filament/include/backend/CallbackHandler.h index 036031a9..9700186d 100644 --- a/package/android/libs/filament/include/backend/CallbackHandler.h +++ b/package/android/libs/filament/include/backend/CallbackHandler.h @@ -64,7 +64,7 @@ class CallbackHandler { virtual void post(void* user, Callback callback) = 0; protected: - virtual ~CallbackHandler() = default; + virtual ~CallbackHandler(); }; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/DescriptorSetOffsetArray.h b/package/android/libs/filament/include/backend/DescriptorSetOffsetArray.h new file mode 100644 index 00000000..d7cfb763 --- /dev/null +++ b/package/android/libs/filament/include/backend/DescriptorSetOffsetArray.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H +#define TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H + +#include + +#include +#include + +#include +#include + + +namespace utils::io { +class ostream; +} // namespace utils::io + +namespace filament::backend { + +void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept; + +class DescriptorSetOffsetArray { +public: + using value_type = uint32_t; + using reference = value_type&; + using const_reference = value_type const&; + using size_type = uint32_t; + using difference_type = int32_t; + using pointer = value_type*; + using const_pointer = value_type const*; + using iterator = pointer; + using const_iterator = const_pointer; + + DescriptorSetOffsetArray() noexcept = default; + + ~DescriptorSetOffsetArray() noexcept = default; + + DescriptorSetOffsetArray(size_type size, DriverApi& driver) noexcept { + mOffsets = (value_type *)allocateFromCommandStream(driver, + size * sizeof(value_type), alignof(value_type)); + std::uninitialized_fill_n(mOffsets, size, 0); + } + + DescriptorSetOffsetArray(std::initializer_list list, DriverApi& driver) noexcept { + mOffsets = (value_type *)allocateFromCommandStream(driver, + list.size() * sizeof(value_type), alignof(value_type)); + std::uninitialized_copy(list.begin(), list.end(), mOffsets); + } + + DescriptorSetOffsetArray(DescriptorSetOffsetArray const&) = delete; + DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray const&) = delete; + + DescriptorSetOffsetArray(DescriptorSetOffsetArray&& rhs) noexcept + : mOffsets(rhs.mOffsets) { + rhs.mOffsets = nullptr; + } + + DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray&& rhs) noexcept { + if (this != &rhs) { + mOffsets = rhs.mOffsets; + rhs.mOffsets = nullptr; + } + return *this; + } + + bool empty() const noexcept { return mOffsets == nullptr; } + + value_type* data() noexcept { return mOffsets; } + const value_type* data() const noexcept { return mOffsets; } + + + reference operator[](size_type n) noexcept { + return *(data() + n); + } + + const_reference operator[](size_type n) const noexcept { + return *(data() + n); + } + + void clear() noexcept { + mOffsets = nullptr; + } + +private: + value_type *mOffsets = nullptr; +}; + +} // namespace filament::backend + +#if !defined(NDEBUG) +utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::DescriptorSetOffsetArray& rhs); +#endif + +#endif //TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H diff --git a/package/android/libs/filament/include/backend/DriverEnums.h b/package/android/libs/filament/include/backend/DriverEnums.h index d69a9991..3b1ae238 100644 --- a/package/android/libs/filament/include/backend/DriverEnums.h +++ b/package/android/libs/filament/include/backend/DriverEnums.h @@ -19,24 +19,32 @@ #ifndef TNT_FILAMENT_BACKEND_DRIVERENUMS_H #define TNT_FILAMENT_BACKEND_DRIVERENUMS_H -#include #include // Because we define ERROR in the FenceStatus enum. #include #include +#include +#include +#include #include -#include +#include +#include #include -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers +#include +#include +#include +#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + /** * Types and enums used by filament's driver. * @@ -93,10 +101,22 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON */ static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40; +/** + * Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the + * given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is + * true. + * + * This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it + * because the swapchain MSAA settings must be configured before window creation. + */ +static constexpr uint64_t SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES = 0x80; + static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3. static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects. static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr size_t MAX_DESCRIPTOR_SET_COUNT = 4; // This is guaranteed by Vulkan. +static constexpr size_t MAX_DESCRIPTOR_COUNT = 64; // per set static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte // of push constant (we assume 4-byte @@ -109,8 +129,8 @@ static constexpr struct { const size_t MAX_FRAGMENT_SAMPLER_COUNT; } FEATURE_LEVEL_CAPS[4] = { { 0, 0 }, // do not use - { 16, 16 }, // guaranteed by OpenGL ES, Vulkan and Metal - { 16, 16 }, // guaranteed by OpenGL ES, Vulkan and Metal + { 16, 16 }, // guaranteed by OpenGL ES, Vulkan, Metal And WebGPU + { 16, 16 }, // guaranteed by OpenGL ES, Vulkan, Metal And WebGPU { 31, 31 }, // guaranteed by Metal }; @@ -121,6 +141,10 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT, static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES. static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr uint8_t EXTERNAL_SAMPLER_DATA_INDEX_UNUSED = + uint8_t(-1);// Case where the descriptor set binding isnt using any external sampler state + // and therefore doesn't have a valid entry. + /** * Defines the backend's feature levels. */ @@ -139,7 +163,8 @@ enum class Backend : uint8_t { OPENGL = 1, //!< Selects the OpenGL/ES driver (default on Android) VULKAN = 2, //!< Selects the Vulkan driver if the platform supports it (default on Linux/Windows) METAL = 3, //!< Selects the Metal driver if the platform supports it (default on MacOS/iOS). - NOOP = 4, //!< Selects the no-op driver for testing purposes. + WEBGPU = 4, //!< Selects the Webgpu driver if the platform supports webgpu. + NOOP = 5, //!< Selects the no-op driver for testing purposes. }; enum class TimerQueryResult : int8_t { @@ -148,7 +173,7 @@ enum class TimerQueryResult : int8_t { AVAILABLE = 1, // result is available }; -static constexpr const char* backendToString(Backend backend) { +constexpr std::string_view to_string(Backend const backend) noexcept { switch (backend) { case Backend::NOOP: return "Noop"; @@ -158,9 +183,12 @@ static constexpr const char* backendToString(Backend backend) { return "Vulkan"; case Backend::METAL: return "Metal"; - default: - return "Unknown"; + case Backend::WEBGPU: + return "WebGPU"; + case Backend::DEFAULT: + return "Default"; } + return "Unknown"; } /** @@ -169,14 +197,16 @@ static constexpr const char* backendToString(Backend backend) { * - The Metal backend can prefer precompiled Metal libraries, while falling back to MSL. */ enum class ShaderLanguage { + UNSPECIFIED = -1, ESSL1 = 0, ESSL3 = 1, SPIRV = 2, MSL = 3, METAL_LIBRARY = 4, + WGSL = 5, }; -static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) { +constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) noexcept { switch (shaderLanguage) { case ShaderLanguage::ESSL1: return "ESSL 1.0"; @@ -188,9 +218,310 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag return "MSL"; case ShaderLanguage::METAL_LIBRARY: return "Metal precompiled library"; + case ShaderLanguage::WGSL: + return "WGSL"; + case ShaderLanguage::UNSPECIFIED: + return "Unspecified"; + } + return "UNKNOWN"; +} + +enum class ShaderStage : uint8_t { + VERTEX = 0, + FRAGMENT = 1, + COMPUTE = 2 +}; + +static constexpr size_t PIPELINE_STAGE_COUNT = 2; +enum class ShaderStageFlags : uint8_t { + NONE = 0, + VERTEX = 0x1, + FRAGMENT = 0x2, + COMPUTE = 0x4, + ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE +}; + +constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept { + switch (type) { + case ShaderStage::VERTEX: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX)); + case ShaderStage::FRAGMENT: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT)); + case ShaderStage::COMPUTE: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE)); + } +} + +enum class TextureType : uint8_t { + FLOAT, + INT, + UINT, + DEPTH, + STENCIL, + DEPTH_STENCIL +}; + +constexpr std::string_view to_string(TextureType type) noexcept { + switch (type) { + case TextureType::FLOAT: return "FLOAT"; + case TextureType::INT: return "INT"; + case TextureType::UINT: return "UINT"; + case TextureType::DEPTH: return "DEPTH"; + case TextureType::STENCIL: return "STENCIL"; + case TextureType::DEPTH_STENCIL: return "DEPTH_STENCIL"; + } + return "UNKNOWN"; +} + + enum class DescriptorType : uint8_t { + SAMPLER_2D_FLOAT, + SAMPLER_2D_INT, + SAMPLER_2D_UINT, + SAMPLER_2D_DEPTH, + + SAMPLER_2D_ARRAY_FLOAT, + SAMPLER_2D_ARRAY_INT, + SAMPLER_2D_ARRAY_UINT, + SAMPLER_2D_ARRAY_DEPTH, + + SAMPLER_CUBE_FLOAT, + SAMPLER_CUBE_INT, + SAMPLER_CUBE_UINT, + SAMPLER_CUBE_DEPTH, + + SAMPLER_CUBE_ARRAY_FLOAT, + SAMPLER_CUBE_ARRAY_INT, + SAMPLER_CUBE_ARRAY_UINT, + SAMPLER_CUBE_ARRAY_DEPTH, + + SAMPLER_3D_FLOAT, + SAMPLER_3D_INT, + SAMPLER_3D_UINT, + + SAMPLER_2D_MS_FLOAT, + SAMPLER_2D_MS_INT, + SAMPLER_2D_MS_UINT, + + SAMPLER_2D_MS_ARRAY_FLOAT, + SAMPLER_2D_MS_ARRAY_INT, + SAMPLER_2D_MS_ARRAY_UINT, + + SAMPLER_EXTERNAL, + UNIFORM_BUFFER, + SHADER_STORAGE_BUFFER, + INPUT_ATTACHMENT, + }; + +constexpr bool isDepthDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_DEPTH: + case DescriptorType::SAMPLER_2D_ARRAY_DEPTH: + case DescriptorType::SAMPLER_CUBE_DEPTH: + case DescriptorType::SAMPLER_CUBE_ARRAY_DEPTH: + return true; + default: ; + } + return false; +} + +constexpr bool isFloatDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_FLOAT: + case DescriptorType::SAMPLER_2D_ARRAY_FLOAT: + case DescriptorType::SAMPLER_CUBE_FLOAT: + case DescriptorType::SAMPLER_CUBE_ARRAY_FLOAT: + case DescriptorType::SAMPLER_3D_FLOAT: + case DescriptorType::SAMPLER_2D_MS_FLOAT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT: + return true; + default: ; + } + return false; +} + +constexpr bool isIntDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_INT: + case DescriptorType::SAMPLER_2D_ARRAY_INT: + case DescriptorType::SAMPLER_CUBE_INT: + case DescriptorType::SAMPLER_CUBE_ARRAY_INT: + case DescriptorType::SAMPLER_3D_INT: + case DescriptorType::SAMPLER_2D_MS_INT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_INT: + return true; + default: ; + } + return false; +} + +constexpr bool isUnsignedIntDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_UINT: + case DescriptorType::SAMPLER_2D_ARRAY_UINT: + case DescriptorType::SAMPLER_CUBE_UINT: + case DescriptorType::SAMPLER_CUBE_ARRAY_UINT: + case DescriptorType::SAMPLER_3D_UINT: + case DescriptorType::SAMPLER_2D_MS_UINT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool is3dTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_3D_FLOAT: + case DescriptorType::SAMPLER_3D_INT: + case DescriptorType::SAMPLER_3D_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool is2dTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_FLOAT: + case DescriptorType::SAMPLER_2D_INT: + case DescriptorType::SAMPLER_2D_UINT: + case DescriptorType::SAMPLER_2D_DEPTH: + case DescriptorType::SAMPLER_2D_MS_FLOAT: + case DescriptorType::SAMPLER_2D_MS_INT: + case DescriptorType::SAMPLER_2D_MS_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool is2dArrayTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_ARRAY_FLOAT: + case DescriptorType::SAMPLER_2D_ARRAY_INT: + case DescriptorType::SAMPLER_2D_ARRAY_UINT: + case DescriptorType::SAMPLER_2D_ARRAY_DEPTH: + case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_INT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool isCubeTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_CUBE_FLOAT: + case DescriptorType::SAMPLER_CUBE_INT: + case DescriptorType::SAMPLER_CUBE_UINT: + case DescriptorType::SAMPLER_CUBE_DEPTH: + return true; + default: ; + } + return false; +} + +constexpr bool isCubeArrayTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_CUBE_ARRAY_FLOAT: + case DescriptorType::SAMPLER_CUBE_ARRAY_INT: + case DescriptorType::SAMPLER_CUBE_ARRAY_UINT: + case DescriptorType::SAMPLER_CUBE_ARRAY_DEPTH: + return true; + default: ; } + return false; } +constexpr bool isMultiSampledTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_MS_FLOAT: + case DescriptorType::SAMPLER_2D_MS_INT: + case DescriptorType::SAMPLER_2D_MS_UINT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_INT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: + return true; + default: ; + } + return false; +} + +constexpr std::string_view to_string(DescriptorType type) noexcept { + #define DESCRIPTOR_TYPE_CASE(TYPE) case DescriptorType::TYPE: return #TYPE; + switch (type) { + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_3D_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_3D_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_3D_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_EXTERNAL) + DESCRIPTOR_TYPE_CASE(UNIFORM_BUFFER) + DESCRIPTOR_TYPE_CASE(SHADER_STORAGE_BUFFER) + DESCRIPTOR_TYPE_CASE(INPUT_ATTACHMENT) + } + return "UNKNOWN"; + #undef DESCRIPTOR_TYPE_CASE +} + +enum class DescriptorFlags : uint8_t { + NONE = 0x00, + + // Indicate a UNIFORM_BUFFER will have dynamic offsets. + DYNAMIC_OFFSET = 0x01, + + // To indicate a texture/sampler type should be unfiltered. + UNFILTERABLE = 0x02, +}; + +using descriptor_set_t = uint8_t; + +using descriptor_binding_t = uint8_t; + +struct DescriptorSetLayoutDescriptor { + static bool isSampler(DescriptorType type) noexcept { + return int(type) <= int(DescriptorType::SAMPLER_EXTERNAL); + } + static bool isBuffer(DescriptorType type) noexcept { + return type == DescriptorType::UNIFORM_BUFFER || + type == DescriptorType::SHADER_STORAGE_BUFFER; + } + DescriptorType type; + ShaderStageFlags stageFlags; + descriptor_binding_t binding; + DescriptorFlags flags = DescriptorFlags::NONE; + uint16_t count = 0; + + friend bool operator==(DescriptorSetLayoutDescriptor const& lhs, + DescriptorSetLayoutDescriptor const& rhs) noexcept { + return lhs.type == rhs.type && + lhs.flags == rhs.flags && + lhs.count == rhs.count && + lhs.stageFlags == rhs.stageFlags; + } +}; + /** * Bitmask for selecting render buffers */ @@ -213,7 +544,7 @@ enum class TargetBufferFlags : uint32_t { ALL = COLOR_ALL | DEPTH | STENCIL //!< Color, depth and stencil buffer selected. }; -inline constexpr TargetBufferFlags getTargetBufferFlagsAt(size_t index) noexcept { +constexpr TargetBufferFlags getTargetBufferFlagsAt(size_t index) noexcept { if (index == 0u) return TargetBufferFlags::COLOR0; if (index == 1u) return TargetBufferFlags::COLOR1; if (index == 2u) return TargetBufferFlags::COLOR2; @@ -228,12 +559,21 @@ inline constexpr TargetBufferFlags getTargetBufferFlagsAt(size_t index) noexcept } /** - * Frequency at which a buffer is expected to be modified and used. This is used as an hint - * for the driver to make better decisions about managing memory internally. + * How the buffer will be used. */ enum class BufferUsage : uint8_t { - STATIC, //!< content modified once, used many times - DYNAMIC, //!< content modified frequently, used many times + STATIC = 0, //!< (legacy) content modified once, used many times + DYNAMIC = 1, //!< (legacy) content modified frequently, used many times + DYNAMIC_BIT = 0x1, //!< buffer can be modified frequently, used many times + SHARED_WRITE_BIT = 0x04, //!< buffer can be memory mapped for write operations +}; + +/** + * How the buffer will be mapped. + */ +enum class MapBufferAccessFlags : uint8_t { + WRITE_BIT = 0x2, //!< buffer is mapped from writing + INVALIDATE_RANGE_BIT = 0x4, //!< the mapped range content is lost }; /** @@ -249,8 +589,19 @@ struct Viewport { int32_t right() const noexcept { return left + int32_t(width); } //! get the top coordinate in window space of the viewport int32_t top() const noexcept { return bottom + int32_t(height); } -}; + friend bool operator==(Viewport const& lhs, Viewport const& rhs) noexcept { + // clang can do this branchless with xor/or + return lhs.left == rhs.left && lhs.bottom == rhs.bottom && + lhs.width == rhs.width && lhs.height == rhs.height; + } + + friend bool operator!=(Viewport const& lhs, Viewport const& rhs) noexcept { + // clang is being dumb and uses branches + return bool(((lhs.left ^ rhs.left) | (lhs.bottom ^ rhs.bottom)) | + ((lhs.width ^ rhs.width) | (lhs.height ^ rhs.height))); + } +}; /** * Specifies the mapping of the near and far clipping plane to window coordinates. @@ -270,15 +621,6 @@ enum class FenceStatus : int8_t { TIMEOUT_EXPIRED = 1, //!< wait()'s timeout expired. The Fence condition is not satisfied. }; -/** - * Status codes for sync objects - */ -enum class SyncStatus : int8_t { - ERROR = -1, //!< An error occurred. The Sync is not signaled. - SIGNALED = 0, //!< The Sync is signaled. - NOT_SIGNALED = 1, //!< The Sync is not signaled yet -}; - static constexpr uint64_t FENCE_WAIT_FOR_EVER = uint64_t(-1); /** @@ -297,6 +639,15 @@ enum class ShaderModel : uint8_t { }; static constexpr size_t SHADER_MODEL_COUNT = 2; +constexpr std::string_view to_string(ShaderModel model) noexcept { + switch (model) { + case ShaderModel::MOBILE: + return "mobile"; + case ShaderModel::DESKTOP: + return "desktop"; + } +} + /** * Primitive types */ @@ -309,6 +660,18 @@ enum class PrimitiveType : uint8_t { TRIANGLE_STRIP = 5 //!< triangle strip }; +[[nodiscard]] constexpr bool isStripPrimitiveType(const PrimitiveType type) { + switch (type) { + case PrimitiveType::POINTS: + case PrimitiveType::LINES: + case PrimitiveType::TRIANGLES: + return false; + case PrimitiveType::LINE_STRIP: + case PrimitiveType::TRIANGLE_STRIP: + return true; + } +} + /** * Supported uniform types */ @@ -350,11 +713,29 @@ enum class Precision : uint8_t { DEFAULT }; +union ConstantValue { + int32_t i; + float f; + bool b; +}; + /** * Shader compiler priority queue + * + * On platforms which support parallel shader compilation, compilation requests will be processed in + * order of priority, then insertion order. See Material::compile(). */ enum class CompilerPriorityQueue : uint8_t { + /** We need this program NOW. + * + * When passed as an argument to Material::compile(), if the platform doesn't support parallel + * compilation, but does support amortized shader compilation, the given shader program will be + * synchronously compiled. + */ + CRITICAL, + /** We will need this program soon. */ HIGH, + /** We will need this program eventually. */ LOW }; @@ -368,6 +749,24 @@ enum class SamplerType : uint8_t { SAMPLER_CUBEMAP_ARRAY, //!< Cube map array texture (feature level 2) }; +constexpr std::string_view to_string(SamplerType const type) noexcept { + switch (type) { + case SamplerType::SAMPLER_2D: + return "SAMPLER_2D"; + case SamplerType::SAMPLER_2D_ARRAY: + return "SAMPLER_2D_ARRAY"; + case SamplerType::SAMPLER_CUBEMAP: + return "SAMPLER_CUBEMAP"; + case SamplerType::SAMPLER_EXTERNAL: + return "SAMPLER_EXTERNAL"; + case SamplerType::SAMPLER_3D: + return "SAMPLER_3D"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "SAMPLER_CUBEMAP_ARRAY"; + } + return "Unknown"; +} + //! Subpass type enum class SubpassType : uint8_t { SUBPASS_INPUT @@ -381,6 +780,20 @@ enum class SamplerFormat : uint8_t { SHADOW = 3 //!< shadow sampler (PCF) }; +constexpr std::string_view to_string(SamplerFormat const format) noexcept { + switch (format) { + case SamplerFormat::INT: + return "INT"; + case SamplerFormat::UINT: + return "UINT"; + case SamplerFormat::FLOAT: + return "FLOAT"; + case SamplerFormat::SHADOW: + return "SHADOW"; + } + return "Unknown"; +} + /** * Supported element types */ @@ -420,6 +833,15 @@ enum class BufferObjectBinding : uint8_t { SHADER_STORAGE }; +constexpr std::string_view to_string(BufferObjectBinding type) noexcept { + switch (type) { + case BufferObjectBinding::VERTEX: return "VERTEX"; + case BufferObjectBinding::UNIFORM: return "UNIFORM"; + case BufferObjectBinding::SHADER_STORAGE: return "SHADER_STORAGE"; + } + return "UNKNOWN"; +} + //! Face culling Mode enum class CullingMode : uint8_t { NONE, //!< No culling, front and back faces are visible @@ -681,6 +1103,8 @@ enum class TextureFormat : uint16_t { SRGB_ALPHA_BPTC_UNORM, // BC7 sRGB }; +TextureType getTextureType(TextureFormat format) noexcept; + //! Bitmask describing the intended Texture Usage enum class TextureUsage : uint16_t { NONE = 0x0000, @@ -693,7 +1117,9 @@ enum class TextureUsage : uint16_t { BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() PROTECTED = 0x0100, //!< Texture can be used for protected content - DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage + GEN_MIPMAPPABLE = 0x0200, //!< Texture can be used with generateMipmaps() + DEFAULT = UPLOADABLE | SAMPLEABLE, //!< Default texture usage + ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments }; //! Texture swizzle @@ -707,7 +1133,7 @@ enum class TextureSwizzle : uint8_t { }; //! returns whether this format a depth format -static constexpr bool isDepthFormat(TextureFormat format) noexcept { +constexpr bool isDepthFormat(TextureFormat format) noexcept { switch (format) { case TextureFormat::DEPTH32F: case TextureFormat::DEPTH24: @@ -720,7 +1146,7 @@ static constexpr bool isDepthFormat(TextureFormat format) noexcept { } } -static constexpr bool isStencilFormat(TextureFormat format) noexcept { +constexpr bool isStencilFormat(TextureFormat format) noexcept { switch (format) { case TextureFormat::STENCIL8: case TextureFormat::DEPTH24_STENCIL8: @@ -731,7 +1157,34 @@ static constexpr bool isStencilFormat(TextureFormat format) noexcept { } } -static constexpr bool isUnsignedIntFormat(TextureFormat format) { +constexpr bool isColorFormat(TextureFormat format) noexcept { + switch (format) { + // Standard color formats + case TextureFormat::R8: + case TextureFormat::RG8: + case TextureFormat::RGBA8: + case TextureFormat::R16F: + case TextureFormat::RG16F: + case TextureFormat::RGBA16F: + case TextureFormat::R32F: + case TextureFormat::RG32F: + case TextureFormat::RGBA32F: + case TextureFormat::RGB10_A2: + case TextureFormat::R11F_G11F_B10F: + case TextureFormat::SRGB8: + case TextureFormat::SRGB8_A8: + case TextureFormat::RGB8: + case TextureFormat::RGB565: + case TextureFormat::RGB5_A1: + case TextureFormat::RGBA4: + return true; + default: + break; + } + return false; +} + +constexpr bool isUnsignedIntFormat(TextureFormat format) { switch (format) { case TextureFormat::R8UI: case TextureFormat::R16UI: @@ -752,7 +1205,7 @@ static constexpr bool isUnsignedIntFormat(TextureFormat format) { } } -static constexpr bool isSignedIntFormat(TextureFormat format) { +constexpr bool isSignedIntFormat(TextureFormat format) { switch (format) { case TextureFormat::R8I: case TextureFormat::R16I: @@ -774,35 +1227,35 @@ static constexpr bool isSignedIntFormat(TextureFormat format) { } //! returns whether this format is a compressed format -static constexpr bool isCompressedFormat(TextureFormat format) noexcept { +constexpr bool isCompressedFormat(TextureFormat format) noexcept { return format >= TextureFormat::EAC_R11; } //! returns whether this format is an ETC2 compressed format -static constexpr bool isETC2Compression(TextureFormat format) noexcept { +constexpr bool isETC2Compression(TextureFormat format) noexcept { return format >= TextureFormat::EAC_R11 && format <= TextureFormat::ETC2_EAC_SRGBA8; } //! returns whether this format is an S3TC compressed format -static constexpr bool isS3TCCompression(TextureFormat format) noexcept { +constexpr bool isS3TCCompression(TextureFormat format) noexcept { return format >= TextureFormat::DXT1_RGB && format <= TextureFormat::DXT5_SRGBA; } -static constexpr bool isS3TCSRGBCompression(TextureFormat format) noexcept { +constexpr bool isS3TCSRGBCompression(TextureFormat format) noexcept { return format >= TextureFormat::DXT1_SRGB && format <= TextureFormat::DXT5_SRGBA; } //! returns whether this format is an RGTC compressed format -static constexpr bool isRGTCCompression(TextureFormat format) noexcept { +constexpr bool isRGTCCompression(TextureFormat format) noexcept { return format >= TextureFormat::RED_RGTC1 && format <= TextureFormat::SIGNED_RED_GREEN_RGTC2; } //! returns whether this format is an BPTC compressed format -static constexpr bool isBPTCCompression(TextureFormat format) noexcept { +constexpr bool isBPTCCompression(TextureFormat format) noexcept { return format >= TextureFormat::RGB_BPTC_SIGNED_FLOAT && format <= TextureFormat::SRGB_ALPHA_BPTC_UNORM; } -static constexpr bool isASTCCompression(TextureFormat format) noexcept { +constexpr bool isASTCCompression(TextureFormat format) noexcept { return format >= TextureFormat::RGBA_ASTC_4x4 && format <= TextureFormat::SRGB8_ALPHA8_ASTC_12x12; } @@ -863,7 +1316,7 @@ enum class SamplerCompareFunc : uint8_t { }; //! Sampler parameters -struct SamplerParams { // NOLINT +struct SamplerParams { // NOLINT SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST) SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST) SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE) @@ -887,6 +1340,9 @@ struct SamplerParams { // NOLINT struct EqualTo { bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept { + assert_invariant(lhs.padding0 == 0); + assert_invariant(lhs.padding1 == 0); + assert_invariant(lhs.padding2 == 0); auto* pLhs = reinterpret_cast(reinterpret_cast(&lhs)); auto* pRhs = reinterpret_cast(reinterpret_cast(&rhs)); return *pLhs == *pRhs; @@ -895,17 +1351,31 @@ struct SamplerParams { // NOLINT struct LessThan { bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept { + assert_invariant(lhs.padding0 == 0); + assert_invariant(lhs.padding1 == 0); + assert_invariant(lhs.padding2 == 0); auto* pLhs = reinterpret_cast(reinterpret_cast(&lhs)); auto* pRhs = reinterpret_cast(reinterpret_cast(&rhs)); - return *pLhs == *pRhs; + return *pLhs < *pRhs; } }; + bool isFiltered() const noexcept { + return filterMag != SamplerMagFilter::NEAREST || filterMin != SamplerMinFilter::NEAREST; + } + private: - friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept { - return SamplerParams::LessThan{}(lhs, rhs); + friend bool operator == (SamplerParams lhs, SamplerParams rhs) noexcept { + return EqualTo{}(lhs, rhs); + } + friend bool operator != (SamplerParams lhs, SamplerParams rhs) noexcept { + return !EqualTo{}(lhs, rhs); + } + friend bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept { + return LessThan{}(lhs, rhs); } }; + static_assert(sizeof(SamplerParams) == 4); // The limitation to 64-bits max comes from how we store a SamplerParams in our JNI code @@ -913,6 +1383,11 @@ static_assert(sizeof(SamplerParams) == 4); static_assert(sizeof(SamplerParams) <= sizeof(uint64_t), "SamplerParams must be no more than 64 bits"); +struct DescriptorSetLayout { + std::variant label; + utils::FixedCapacityVector descriptors; +}; + //! blending equation function enum class BlendEquation : uint8_t { ADD, //!< the fragment is added to the color buffer @@ -1058,7 +1533,7 @@ struct RasterState { bool inverseFrontFaces : 1; // 31 //! padding, must be 0 - uint8_t padding : 1; // 32 + bool depthClamp : 1; // 32 }; uint32_t u = 0; }; @@ -1069,32 +1544,6 @@ struct RasterState { * \privatesection */ -enum class ShaderStage : uint8_t { - VERTEX = 0, - FRAGMENT = 1, - COMPUTE = 2 -}; - -static constexpr size_t PIPELINE_STAGE_COUNT = 2; -enum class ShaderStageFlags : uint8_t { - NONE = 0, - VERTEX = 0x1, - FRAGMENT = 0x2, - COMPUTE = 0x4, - ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE -}; - -static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept { - switch (type) { - case ShaderStage::VERTEX: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX)); - case ShaderStage::FRAGMENT: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT)); - case ShaderStage::COMPUTE: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE)); - } -} - /** * Selects which buffers to clear at the beginning of the render pass, as well as which buffers * can be discarded at the beginning and end of the render pass. @@ -1232,7 +1681,7 @@ static_assert(sizeof(StencilState::StencilOperations) == 5u, static_assert(sizeof(StencilState) == 12u, "StencilState size not what was intended"); -using FrameScheduledCallback = utils::Invocable; +using FrameScheduledCallback = utils::Invocable; enum class Workaround : uint16_t { // The EASU pass must split because shader compiler flattens early-exit branch @@ -1243,15 +1692,34 @@ enum class Workaround : uint16_t { // for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu ADRENO_UNIFORM_ARRAY_CRASH, // Workaround a Metal pipeline compilation error with the message: - // "Could not statically determine the target of a texture". See light_indirect.fs - A8X_STATIC_TEXTURE_TARGET_ERROR, + // "Could not statically determine the target of a texture". See surface_light_indirect.fs + METAL_STATIC_TEXTURE_TARGET_ERROR, // Adreno drivers sometimes aren't able to blit into a layer of a texture array. DISABLE_BLIT_INTO_TEXTURE_ARRAY, // Multiple workarounds needed for PowerVR GPUs POWER_VR_SHADER_WORKAROUNDS, + // Some browsers, such as Firefox on Mac, struggle with slow shader compile/link times when + // creating programs for the default material, leading to startup stutters. This workaround + // prevents these stutters by not precaching depth variants of the default material for those + // particular browsers. + DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL, + // Emulate an sRGB swapchain in shader code. + EMULATE_SRGB_SWAPCHAIN, }; -using StereoscopicType = backend::Platform::StereoscopicType; +using StereoscopicType = Platform::StereoscopicType; + +using FrameTimestamps = Platform::FrameTimestamps; + +using CompositorTiming = Platform::CompositorTiming; + +using AsynchronousMode = Platform::AsynchronousMode; + +using AsyncCallId = uint32_t; + +static constexpr AsyncCallId InvalidAsyncCallId = std::numeric_limits::max(); + +using AsynchronousMode = Platform::AsynchronousMode; } // namespace filament::backend @@ -1259,10 +1727,17 @@ template<> struct utils::EnableBitMaskOperators struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; template<> struct utils::EnableBitMaskOperators : public std::true_type {}; template<> struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; + template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> struct utils::EnableIntegerOperators @@ -1291,12 +1766,16 @@ utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::Textu utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::TextureUsage usage); utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::BufferObjectBinding binding); utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::TextureSwizzle swizzle); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::ShaderStage shaderStage); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::ShaderStageFlags stageFlags); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::CompilerPriorityQueue compilerPriorityQueue); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::PushConstantVariant pushConstantVariant); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::AttributeArray& type); +utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::DescriptorSetLayout& dsl); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::PolygonOffset& po); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::RasterState& rs); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::RenderPassParams& b); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::Viewport& v); -utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::ShaderStageFlags stageFlags); #endif #endif // TNT_FILAMENT_BACKEND_DRIVERENUMS_H diff --git a/package/android/libs/filament/include/backend/Handle.h b/package/android/libs/filament/include/backend/Handle.h index c54e9609..c9b123c0 100644 --- a/package/android/libs/filament/include/backend/Handle.h +++ b/package/android/libs/filament/include/backend/Handle.h @@ -17,15 +17,17 @@ #ifndef TNT_FILAMENT_BACKEND_HANDLE_H #define TNT_FILAMENT_BACKEND_HANDLE_H -#if !defined(NDEBUG) -#include -#endif #include #include // FIXME: STL headers are not allowed in public headers +#include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { struct HwBufferObject; @@ -34,13 +36,16 @@ struct HwIndexBuffer; struct HwProgram; struct HwRenderPrimitive; struct HwRenderTarget; -struct HwSamplerGroup; struct HwStream; struct HwSwapChain; +struct HwSync; struct HwTexture; struct HwTimerQuery; struct HwVertexBufferInfo; struct HwVertexBuffer; +struct HwDescriptorSetLayout; +struct HwDescriptorSet; +struct HwMemoryMappedBuffer; /* * A handle to a backend resource. HandleBase is for internal use only. @@ -104,8 +109,18 @@ struct Handle : public HandleBase { Handle(Handle const& rhs) noexcept = default; Handle(Handle&& rhs) noexcept = default; - Handle& operator=(Handle const& rhs) noexcept = default; - Handle& operator=(Handle&& rhs) noexcept = default; + // Explicitly redefine copy/move assignment operators rather than just using default here. + // Because it doesn't make a call to the parent's method automatically during the std::move + // function call(https://en.cppreference.com/w/cpp/algorithm/move) in certain compilers like + // NDK 25.1.8937393 and below (see b/371980551) + Handle& operator=(Handle const& rhs) noexcept { + HandleBase::operator=(rhs); + return *this; + } + Handle& operator=(Handle&& rhs) noexcept { + HandleBase::operator=(std::move(rhs)); + return *this; + } explicit Handle(HandleId id) noexcept : HandleBase(id) { } @@ -118,7 +133,7 @@ struct Handle : public HandleBase { bool operator>=(const Handle& rhs) const noexcept { return getId() >= rhs.getId(); } // type-safe Handle cast - template::value> > + template> > Handle(Handle const& base) noexcept : HandleBase(base) { } // NOLINT(hicpp-explicit-conversions,google-explicit-constructor) private: @@ -130,19 +145,22 @@ struct Handle : public HandleBase { // Types used by the command stream // (we use this renaming because the macro-system doesn't deal well with "<" and ">") -using BufferObjectHandle = Handle; -using FenceHandle = Handle; -using IndexBufferHandle = Handle; -using ProgramHandle = Handle; -using RenderPrimitiveHandle = Handle; -using RenderTargetHandle = Handle; -using SamplerGroupHandle = Handle; -using StreamHandle = Handle; -using SwapChainHandle = Handle; -using TextureHandle = Handle; -using TimerQueryHandle = Handle; -using VertexBufferHandle = Handle; -using VertexBufferInfoHandle = Handle; +using BufferObjectHandle = Handle; +using FenceHandle = Handle; +using IndexBufferHandle = Handle; +using ProgramHandle = Handle; +using RenderPrimitiveHandle = Handle; +using RenderTargetHandle = Handle; +using StreamHandle = Handle; +using SwapChainHandle = Handle; +using SyncHandle = Handle; +using TextureHandle = Handle; +using TimerQueryHandle = Handle; +using VertexBufferHandle = Handle; +using VertexBufferInfoHandle = Handle; +using DescriptorSetLayoutHandle = Handle; +using DescriptorSetHandle = Handle; +using MemoryMappedBufferHandle = Handle; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/PipelineState.h b/package/android/libs/filament/include/backend/PipelineState.h index 220d04bb..106e579e 100644 --- a/package/android/libs/filament/include/backend/PipelineState.h +++ b/package/android/libs/filament/include/backend/PipelineState.h @@ -20,17 +20,27 @@ #include #include -#include +#include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { //! \privatesection +struct PipelineLayout { + using SetLayout = std::array, MAX_DESCRIPTOR_SET_COUNT>; + SetLayout setLayout; // 16 +}; + struct PipelineState { Handle program; // 4 Handle vertexBufferInfo; // 4 + PipelineLayout pipelineLayout; // 16 RasterState rasterState; // 4 StencilState stencilState; // 12 PolygonOffset polygonOffset; // 8 diff --git a/package/android/libs/filament/include/backend/PixelBufferDescriptor.h b/package/android/libs/filament/include/backend/PixelBufferDescriptor.h index c45f344d..93f82c9d 100644 --- a/package/android/libs/filament/include/backend/PixelBufferDescriptor.h +++ b/package/android/libs/filament/include/backend/PixelBufferDescriptor.h @@ -24,11 +24,14 @@ #include #include -#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { /** @@ -201,23 +204,15 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { }, new T(std::forward(functor)) }; } - - // -------------------------------------------------------------------------------------------- - /** - * Computes the size in bytes needed to fit an image of given dimensions and format + * Computes the size in bytes for a pixel of given dimensions and format * * @param format Format of the image pixels * @param type Type of the image pixels - * @param stride Stride of a row in pixels - * @param height Height of the image in rows - * @param alignment Alignment in bytes of pixel rows - * @return The buffer size needed to fit this image in bytes + * @return The size of the specified pixel in bytes */ - static constexpr size_t computeDataSize(PixelDataFormat format, PixelDataType type, - size_t stride, size_t height, size_t alignment) noexcept { - assert_invariant(alignment); + static constexpr size_t computePixelSize(PixelDataFormat format, PixelDataType type) noexcept { if (type == PixelDataType::COMPRESSED) { return 0; } @@ -239,7 +234,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { case PixelDataFormat::RGB_INTEGER: n = 3; break; - case PixelDataFormat::UNUSED: // shouldn't happen (used to be rgbm) + case PixelDataFormat::UNUSED:// shouldn't happen (used to be rgbm) case PixelDataFormat::RGBA: case PixelDataFormat::RGBA_INTEGER: n = 4; @@ -248,7 +243,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { size_t bpp = n; switch (type) { - case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings + case PixelDataType::COMPRESSED:// Impossible -- to squash the IDE warnings case PixelDataType::UBYTE: case PixelDataType::BYTE: // nothing to do @@ -279,16 +274,35 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { bpp = 2; break; } + return bpp; + } + + // -------------------------------------------------------------------------------------------- + + /** + * Computes the size in bytes needed to fit an image of given dimensions and format + * + * @param format Format of the image pixels + * @param type Type of the image pixels + * @param stride Stride of a row in pixels + * @param height Height of the image in rows + * @param alignment Alignment in bytes of pixel rows + * @return The buffer size needed to fit this image in bytes + */ + static constexpr size_t computeDataSize(PixelDataFormat format, PixelDataType type, + size_t stride, size_t height, size_t alignment) noexcept { + assert_invariant(alignment); + size_t bpp = computePixelSize(format, type); size_t const bpr = bpp * stride; size_t const bprAligned = (bpr + (alignment - 1)) & (~alignment + 1); return bprAligned * height; } //! left coordinate in pixels - uint32_t left = 0; + uint32_t left = 0; //! top coordinate in pixels - uint32_t top = 0; + uint32_t top = 0; union { struct { //! stride in pixels diff --git a/package/android/libs/filament/include/backend/Platform.h b/package/android/libs/filament/include/backend/Platform.h index 4f73c437..dcbc9928 100644 --- a/package/android/libs/filament/include/backend/Platform.h +++ b/package/android/libs/filament/include/backend/Platform.h @@ -19,14 +19,21 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORM_H +#include #include #include +#include #include #include +#include +#include +#include + namespace filament::backend { +class CallbackHandler; class Driver; /** @@ -40,6 +47,163 @@ class UTILS_PUBLIC Platform { struct SwapChain {}; struct Fence {}; struct Stream {}; + struct Sync {}; + + using SyncCallback = void(*)(Sync* UTILS_NONNULL sync, void* UTILS_NULLABLE userData); + + class ExternalImageHandle; + + class ExternalImage { + friend class ExternalImageHandle; + std::atomic_uint32_t mRefCount{0}; + protected: + virtual ~ExternalImage() noexcept; + }; + + class ExternalImageHandle { + ExternalImage* UTILS_NULLABLE mTarget = nullptr; + static void incref(ExternalImage* UTILS_NULLABLE p) noexcept; + static void decref(ExternalImage* UTILS_NULLABLE p) noexcept; + + public: + ExternalImageHandle() noexcept; + ~ExternalImageHandle() noexcept; + explicit ExternalImageHandle(ExternalImage* UTILS_NULLABLE p) noexcept; + ExternalImageHandle(ExternalImageHandle const& rhs) noexcept; + ExternalImageHandle(ExternalImageHandle&& rhs) noexcept; + ExternalImageHandle& operator=(ExternalImageHandle const& rhs) noexcept; + ExternalImageHandle& operator=(ExternalImageHandle&& rhs) noexcept; + + bool operator==(const ExternalImageHandle& rhs) const noexcept { + return mTarget == rhs.mTarget; + } + explicit operator bool() const noexcept { return mTarget != nullptr; } + + ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; } + ExternalImage const* UTILS_NULLABLE get() const noexcept { return mTarget; } + + ExternalImage* UTILS_NULLABLE operator->() noexcept { return mTarget; } + ExternalImage const* UTILS_NULLABLE operator->() const noexcept { return mTarget; } + + ExternalImage& operator*() noexcept { return *mTarget; } + ExternalImage const& operator*() const noexcept { return *mTarget; } + + void clear() noexcept; + void reset(ExternalImage* UTILS_NULLABLE p) noexcept; + + private: + friend utils::io::ostream& operator<<(utils::io::ostream& out, + ExternalImageHandle const& handle); + }; + + using ExternalImageHandleRef = ExternalImageHandle const&; + + struct CompositorTiming { + /** duration in nanosecond since epoch of std::steady_clock */ + using time_point_ns = int64_t; + /** duration in nanosecond on the std::steady_clock */ + using duration_ns = int64_t; + static constexpr time_point_ns INVALID = -1; //!< value not supported + /** + * The timestamp [ns] since epoch of the next time the compositor will begin composition. + * This is effectively the deadline for when the compositor must receive a newly queued + * frame. + */ + time_point_ns compositeDeadline; + + /** + * The time delta [ns] between subsequent composition events. + */ + duration_ns compositeInterval; + + /** + * The time delta [ns] between the start of composition and the expected present time of + * that composition. This can be used to estimate the latency of the actual present time. + */ + duration_ns compositeToPresentLatency; + + /** + * The timestamp [ns] since epoch of the system's expected presentation time. + * INVALID if not supported. + */ + time_point_ns expectedPresentTime; + + /** + * The timestamp [ns] since epoch of the current frame's start (i.e. vsync) + * INVALID if not supported. + */ + time_point_ns frameTime; + + /** + * The timestamp [ns] since epoch of the current frame's deadline + * INVALID if not supported. + */ + time_point_ns frameTimelineDeadline; + }; + + struct FrameTimestamps { + /** duration in nanosecond since epoch of std::steady_clock */ + using time_point_ns = int64_t; + static constexpr time_point_ns INVALID = -1; //!< value not supported + static constexpr time_point_ns PENDING = -2; //!< value not yet available + + /** + * The time the application requested this frame be presented. + * If the application does not request a presentation time explicitly, + * this will correspond to buffer's queue time. + */ + time_point_ns requestedPresentTime; + + /** + * The time when all the application's rendering to the surface was completed. + */ + time_point_ns acquireTime; + + /** + * The time when the compositor selected this frame as the one to use for the next + * composition. This is the earliest indication that the frame was submitted in time. + */ + time_point_ns latchTime; + + /** + * The first time at which the compositor began preparing composition for this frame. + * Zero if composition was handled by the display and the compositor didn't do any + * rendering. + */ + time_point_ns firstCompositionStartTime; + + /** + * The last time at which the compositor began preparing composition for this frame, for + * frames composited more than once. Zero if composition was handled by the display and the + * compositor didn't do any rendering. + */ + time_point_ns lastCompositionStartTime; + + /** + * The time at which the compositor's rendering work for this frame finished. This will be + * INVALID if composition was handled by the display and the compositor didn't do any + * rendering. + */ + time_point_ns gpuCompositionDoneTime; + + /** + * The time at which this frame started to scan out to the physical display. + */ + time_point_ns displayPresentTime; + + /** + * The time when the buffer became available for reuse as a buffer the client can target + * without blocking. This is generally the point when all read commands of the buffer have + * been submitted, but not necessarily completed. + */ + time_point_ns dequeueReadyTime; + + /** + * The time at which all reads for the purpose of display/composition were completed for + * this frame. + */ + time_point_ns releaseTime; + }; /** * The type of technique for stereoscopic rendering. (Note that the materials used will need to @@ -61,6 +225,58 @@ class UTILS_PUBLIC Platform { MULTIVIEW, }; + /** + * This controls the priority level for GPU work scheduling, which helps prioritize the + * submitted GPU work and enables preemption. + */ + enum class GpuContextPriority : uint8_t { + /** + * Backend default GPU context priority (typically MEDIUM) + */ + DEFAULT, + /** + * For non-interactive, deferrable workloads. This should not interfere with standard + * applications. + */ + LOW, + /** + * The default priority level for standard applications. + */ + MEDIUM, + /** + * For high-priority, latency-sensitive workloads that are more important than standard + * applications. + */ + HIGH, + /** + * The highest priority, intended for system-critical, real-time applications where missing + * deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks). + */ + REALTIME, + }; + + /** + * Defines how asynchronous operations are handled by the engine. + */ + enum class AsynchronousMode : uint8_t { + /** + * Asynchronous operations are disabled. This is the default. + */ + NONE, + + /** + * Attempts to use a dedicated worker thread for asynchronous tasks. If threading is not + * supported by the platform, it automatically falls back to using an amortization strategy. + */ + THREAD_PREFERRED, + + /** + * Uses an amortization strategy, processing a small number of asynchronous tasks during + * each engine update cycle. + */ + AMORTIZATION, + }; + struct DriverConfig { /** * Size of handle arena in bytes. Setting to 0 indicates default value is to be used. @@ -68,24 +284,31 @@ class UTILS_PUBLIC Platform { */ size_t handleArenaSize = 0; - /** - * This number of most-recently destroyed textures will be tracked for use-after-free. - * Throws an exception when a texture is freed but still bound to a SamplerGroup and used in - * a draw call. 0 disables completely. Currently only respected by the Metal backend. - */ - size_t textureUseAfterFreePoolSize = 0; + size_t metalUploadBufferSizeBytes = 512 * 1024; /** * Set to `true` to forcibly disable parallel shader compilation in the backend. - * Currently only honored by the GL and Metal backends. + * Currently only honored by the GL and Metal backends, and the Vulkan backend + * when some experimental features are enabled. */ bool disableParallelShaderCompile = false; + /** + * Set to `true` to forcibly disable amortized shader compilation in the backend. + * Currently only honored by the GL backend. + */ + bool disableAmortizedShaderCompile = true; + /** * Disable backend handles use-after-free checks. */ bool disableHandleUseAfterFreeCheck = false; + /** + * Disable backend handles tags for heap allocated (fallback) handles + */ + bool disableHeapHandleTags = false; + /** * Force GLES2 context if supported, or pretend the context is ES2. Only meaningful on * GLES 3.x backends. @@ -96,6 +319,50 @@ class UTILS_PUBLIC Platform { * Sets the technique for stereoscopic rendering. */ StereoscopicType stereoscopicType = StereoscopicType::NONE; + + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + */ + bool assertNativeWindowIsValid = false; + + /** + * The action to take if a Drawable cannot be acquired. If true, the + * frame is aborted instead of panic. This is only supported for: + * - PlatformMetal + */ + bool metalDisablePanicOnDrawableFailure = false; + + /** + * GPU context priority level. Controls GPU work scheduling and preemption. + * This is only supported for: + * - PlatformEGL + */ + GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT; + + /** + * Enables asynchronous pipeline cache preloading, if supported on this device. + * This is only supported for: + * - VulkanPlatform + * When the following device extensions are available: + * - VK_KHR_dynamic_rendering + * - VK_EXT_vertex_input_dynamic_state + * Should be enabled only for devices where it has been shown this is effective. + */ + bool vulkanEnableAsyncPipelineCachePrewarming = false; + + /** + * Bypass the staging buffer because the device is of Unified Memory Architecture. + * This is only supported for: + * - VulkanPlatform + */ + bool vulkanEnableStagingBufferBypass = false; + + /** + * Asynchronous mode for the engine. Defines how asynchronous operations are handled. + */ + AsynchronousMode asynchronousMode = AsynchronousMode::NONE; }; Platform() noexcept; @@ -116,13 +383,13 @@ class UTILS_PUBLIC Platform { * @param sharedContext an optional shared context. This is not meaningful with all graphic * APIs and platforms. * For EGL platforms, this is an EGLContext. - * + * * @param driverConfig specifies driver initialization parameters * * @return nullptr on failure, or a pointer to the newly created driver. */ - virtual backend::Driver* UTILS_NULLABLE createDriver(void* UTILS_NULLABLE sharedContext, - const DriverConfig& driverConfig) noexcept = 0; + virtual Driver* UTILS_NULLABLE createDriver(void* UTILS_NULLABLE sharedContext, + const DriverConfig& driverConfig) = 0; /** * Processes the platform's event queue when called from its primary event-handling thread. @@ -133,6 +400,65 @@ class UTILS_PUBLIC Platform { */ virtual bool pumpEvents() noexcept; + // -------------------------------------------------------------------------------------------- + // Swapchain timing APIs + + /** + * Whether this platform supports compositor timing querying. + * + * @return true if this Platform supports compositor timings, false otherwise [default] + * @see queryCompositorTiming() + * @see setPresentFrameId() + * @see queryFrameTimestamps() + */ + virtual bool isCompositorTimingSupported() const noexcept; + + /** + * If compositor timing is supported, fills the provided CompositorTiming structure + * with timing information form the compositor the swapchain's native window is using. + * The swapchain'snative window must be valid (i.e. not a headless swapchain). + * @param swapchain to query the compositor timing from + * @return true on success, false otherwise (e.g. if not supported) + * @see isCompositorTimingSupported() + */ + virtual bool queryCompositorTiming(SwapChain const* UTILS_NONNULL swapchain, + CompositorTiming* UTILS_NONNULL outCompositorTiming) const noexcept; + + /** + * Associate a generic frameId which must be monotonically increasing (albeit not strictly) with + * the next frame to be presented on the specified swapchain. + * + * This must be called from the backend thread. + * + * @param swapchain + * @param frameId + * @return true on success, false otherwise + * @see isCompositorTimingSupported() + * @see queryFrameTimestamps() + */ + virtual bool setPresentFrameId(SwapChain const* UTILS_NONNULL swapchain, + uint64_t frameId) noexcept; + + /** + * If compositor timing is supported, fills the provided FrameTimestamps structure + * with timing information of a given frame, identified by the frame id, of the specified + * swapchain. The system only keeps a limited history of frames timings. + * + * This API is thread safe and can be called from any thread. + * + * @param swapchain swapchain to query the timestamps of + * @param frameId frame we're interested it + * @param outFrameTimestamps output structure receiving the timestamps + * @return true if successful, false otherwise + * @see isCompositorTimingSupported() + * @see setPresentFrameId() + */ + virtual bool queryFrameTimestamps(SwapChain const* UTILS_NONNULL swapchain, + uint64_t frameId, FrameTimestamps* UTILS_NONNULL outFrameTimestamps) const noexcept; + + // -------------------------------------------------------------------------------------------- + // Caching APIs + /** * InsertBlobFunc is an Invocable to an application-provided function that a * backend implementation may use to insert a key/value pair into the @@ -225,13 +551,25 @@ class UTILS_PUBLIC Platform { size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize, void* UTILS_NONNULL value, size_t valueSize); - using DebugUpdateStatFunc = utils::Invocable; + // -------------------------------------------------------------------------------------------- + // Debugging APIs + + using DebugUpdateStatFunc = utils::Invocable; /** * Sets the callback function that the backend can use to update backend-specific statistics * to aid with debugging. This callback is guaranteed to be called on the Filament driver * thread. * + * The callback signature is (key, intValue, stringValue). Note that for any given call, + * only one of the value parameters (intValue or stringValue) will be meaningful, depending on + * the specific key. + * + * IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking + * work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should + * be made, which could interfere with Filament's driver state. + * * @param debugUpdateStat an Invocable that updates debug statistics */ void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept; @@ -250,15 +588,32 @@ class UTILS_PUBLIC Platform { * This function is guaranteed to be called only on a single thread, the Filament driver * thread. * - * @param key a null-terminated C-string with the key of the debug statistic - * @param value the updated value of key + * @param key a null-terminated C-string with the key of the debug statistic + * @param intValue the updated integer value of key (the string value passed to the + * callback will be empty) + */ + void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue); + + /** + * To track backend-specific statistics, the backend implementation can call the + * application-provided callback function debugUpdateStatFunc to associate or update a value + * with a given key. It is possible for this function to be called multiple times with the + * same key, in which case newer values should overwrite older values. + * + * This function is guaranteed to be called only on a single thread, the Filament driver + * thread. + * + * @param key a null-terminated C-string with the key of the debug statistic + * @param stringValue the updated string value of key (the integer value passed to the + * callback will be 0) */ - void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value); + void debugUpdateStat(const char* UTILS_NONNULL key, utils::CString stringValue); private: - InsertBlobFunc mInsertBlob; - RetrieveBlobFunc mRetrieveBlob; - DebugUpdateStatFunc mDebugUpdateStat; + std::shared_ptr mInsertBlob; + std::shared_ptr mRetrieveBlob; + std::shared_ptr mDebugUpdateStat; + mutable utils::Mutex mMutex; }; } // namespace filament diff --git a/package/android/libs/filament/include/backend/PresentCallable.h b/package/android/libs/filament/include/backend/PresentCallable.h index f37d7704..ea3380c6 100644 --- a/package/android/libs/filament/include/backend/PresentCallable.h +++ b/package/android/libs/filament/include/backend/PresentCallable.h @@ -27,12 +27,12 @@ namespace filament::backend { * A PresentCallable is a callable object that, when called, schedules a frame for presentation on * a SwapChain. * - * Typically, Filament's backend is responsible scheduling a frame's presentation. However, there - * are certain cases where the application might want to control when a frame is scheduled for + * Typically, Filament's backend is responsible for scheduling a frame's presentation. However, + * there are certain cases where the application might want to control when a frame is scheduled for * presentation. * * For example, on iOS, UIKit elements can be synchronized to 3D content by scheduling a present - * within a CATransation: + * within a CATransaction: * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * void myFrameScheduledCallback(PresentCallable presentCallable, void* user) { @@ -55,12 +55,13 @@ namespace filament::backend { * } * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * - * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other - * backends ignore the callback (which will never be called) and proceed normally. + * @remark The PresentCallable mechanism for user-controlled presentation is only supported by + * Filament's Metal backend. On other backends, the FrameScheduledCallback is still invoked, but the + * PresentCallable passed to it is a no-op and calling it has no effect. * - * Applications *must* call each PresentCallable they receive. Each PresentCallable represents a - * frame that is waiting to be presented. If an application fails to call a PresentCallable, a - * memory leak could occur. To "cancel" the presentation of a frame, pass false to the + * When using the Metal backend, applications *must* call each PresentCallable they receive. Each + * PresentCallable represents a frame that is waiting to be presented, and failing to call it + * will result in a memory leak. To "cancel" the presentation of a frame, pass false to the * PresentCallable, which will cancel the presentation of the frame and release associated memory. * * @see Renderer, SwapChain::setFrameScheduledCallback @@ -69,6 +70,7 @@ class UTILS_PUBLIC PresentCallable { public: using PresentFn = void(*)(bool presentFrame, void* user); + static void noopPresent(bool, void*) {} PresentCallable(PresentFn fn, void* user) noexcept; ~PresentCallable() noexcept = default; diff --git a/package/android/libs/filament/include/backend/Program.h b/package/android/libs/filament/include/backend/Program.h index 7cec72cd..431e939e 100644 --- a/package/android/libs/filament/include/backend/Program.h +++ b/package/android/libs/filament/include/backend/Program.h @@ -20,17 +20,21 @@ #include #include #include -#include #include -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers +#include +#include +#include +#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { class Program { @@ -40,36 +44,40 @@ class Program { static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT; static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT; - struct Sampler { - utils::CString name = {}; // name of the sampler in the shader - uint32_t binding = 0; // binding point of the sampler in the shader + struct Descriptor { + utils::CString name; + DescriptorType type; + descriptor_binding_t binding; }; - struct SamplerGroupData { - utils::FixedCapacityVector samplers; - ShaderStageFlags stageFlags = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; - }; + using SpecializationConstant = std::variant; + using DescriptorSetLayoutArray = std::array; - struct Uniform { + struct Uniform { // For ES2 support utils::CString name; // full qualified name of the uniform field uint16_t offset; // offset in 'uint32_t' into the uniform buffer uint8_t size; // >1 for arrays UniformType type; // uniform type }; - using UniformBlockInfo = std::array; - using UniformInfo = utils::FixedCapacityVector; - using SamplerGroupInfo = std::array; + using DescriptorBindingsInfo = utils::FixedCapacityVector; + using DescriptorSetInfo = std::array; + using SpecializationConstantsInfo = utils::FixedCapacityVector; using ShaderBlob = utils::FixedCapacityVector; using ShaderSource = std::array; + using AttributesInfo = utils::FixedCapacityVector>; + using UniformInfo = utils::FixedCapacityVector; + using BindingUniformsInfo = utils::FixedCapacityVector< + std::tuple>; + Program() noexcept; Program(const Program& rhs) = delete; Program& operator=(const Program& rhs) = delete; Program(Program&& rhs) noexcept; - Program& operator=(Program&& rhs) noexcept = delete; + Program& operator=(Program&& rhs) noexcept; ~Program() noexcept; @@ -77,45 +85,22 @@ class Program { // sets the material name and variant for diagnostic purposes only Program& diagnostics(utils::CString const& name, - utils::Invocable&& logger); + utils::Invocable&& logger); - // sets one of the program's shader (e.g. vertex, fragment) + // Sets one of the program's shader (e.g. vertex, fragment) // string-based shaders are null terminated, consequently the size parameter must include the // null terminating character. Program& shader(ShaderStage shader, void const* data, size_t size); - // sets the language of the shader sources provided with shader() (defaults to ESSL3) + // Sets the language of the shader sources provided with shader() (defaults to ESSL3) Program& shaderLanguage(ShaderLanguage shaderLanguage); - // Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is - // not permitted in glsl. The backend needs a way to associate a uniform block - // to a binding point. - Program& uniformBlockBindings( - utils::FixedCapacityVector> const& uniformBlockBindings) noexcept; - - // Note: This is only needed for GLES2.0, this is used to emulate UBO. This function tells - // the program everything it needs to know about the uniforms at a given binding - Program& uniforms(uint32_t index, UniformInfo const& uniforms) noexcept; - - // Note: This is only needed for GLES2.0. - Program& attributes( - utils::FixedCapacityVector> attributes) noexcept; - - // sets the 'bindingPoint' sampler group descriptor for this program. - // 'samplers' can be destroyed after this call. - // This effectively associates a set of (BindingPoints, index) to a texture unit in the shader. - // Or more precisely, what layout(binding=) is set to in GLSL. - Program& setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags, - Sampler const* samplers, size_t count) noexcept; - - struct SpecializationConstant { - using Type = std::variant; - uint32_t id; // id set in glsl - Type value; // value and type - }; + // Descriptor binding (set, binding, type -> shader name) info + Program& descriptorBindings(backend::descriptor_set_t set, + DescriptorBindingsInfo descriptorBindings) noexcept; - Program& specializationConstants( - utils::FixedCapacityVector specConstants) noexcept; + Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept; struct PushConstant { utils::CString name; @@ -129,33 +114,50 @@ class Program { Program& multiview(bool multiview) noexcept; - ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } - ShaderSource& getShadersSource() noexcept { return mShadersSource; } - - UniformBlockInfo const& getUniformBlockBindings() const noexcept { return mUniformBlocks; } - UniformBlockInfo& getUniformBlockBindings() noexcept { return mUniformBlocks; } + // For ES2 support only... + Program& uniforms(uint32_t index, utils::CString name, UniformInfo uniforms); + Program& attributes(AttributesInfo attributes) noexcept; - SamplerGroupInfo const& getSamplerGroupInfo() const { return mSamplerGroups; } - SamplerGroupInfo& getSamplerGroupInfo() { return mSamplerGroups; } + // + // Getters for program construction... + // - auto const& getBindingUniformInfo() const { return mBindingUniformInfo; } - auto& getBindingUniformInfo() { return mBindingUniformInfo; } - - auto const& getAttributes() const { return mAttributes; } - auto& getAttributes() { return mAttributes; } + ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } + ShaderSource& getShadersSource() noexcept { return mShadersSource; } utils::CString const& getName() const noexcept { return mName; } utils::CString& getName() noexcept { return mName; } auto const& getShaderLanguage() const { return mShaderLanguage; } - utils::FixedCapacityVector const& getSpecializationConstants() const noexcept { + uint64_t getCacheId() const noexcept { return mCacheId; } + + bool isMultiview() const noexcept { return mMultiview; } + + CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } + + SpecializationConstantsInfo const& getSpecializationConstants() const noexcept { return mSpecializationConstants; } - utils::FixedCapacityVector& getSpecializationConstants() noexcept { + + SpecializationConstantsInfo& getSpecializationConstants() noexcept { return mSpecializationConstants; } + DescriptorSetInfo& getDescriptorBindings() noexcept { + return mDescriptorBindings; + } + + inline Program& descriptorLayout(backend::descriptor_set_t set, + DescriptorSetLayout descriptorLayout) noexcept { + mDescriptorLayouts[set] = std::move(descriptorLayout); + return *this; + } + + const DescriptorSetLayoutArray& getDescriptorSetLayouts() const noexcept { + return mDescriptorLayouts; + } + utils::FixedCapacityVector const& getPushConstants( ShaderStage stage) const noexcept { return mPushConstants[static_cast(stage)]; @@ -165,27 +167,34 @@ class Program { return mPushConstants[static_cast(stage)]; } - uint64_t getCacheId() const noexcept { return mCacheId; } + auto const& getBindingUniformInfo() const { return mBindingUniformsInfo; } + auto& getBindingUniformInfo() { return mBindingUniformsInfo; } - bool isMultiview() const noexcept { return mMultiview; } - - CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } + auto const& getAttributes() const { return mAttributes; } + auto& getAttributes() { return mAttributes; } private: friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder); - UniformBlockInfo mUniformBlocks = {}; - SamplerGroupInfo mSamplerGroups = {}; ShaderSource mShadersSource; ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3; utils::CString mName; uint64_t mCacheId{}; - utils::Invocable mLogger; - utils::FixedCapacityVector mSpecializationConstants; - std::array, SHADER_TYPE_COUNT> mPushConstants; - utils::FixedCapacityVector> mAttributes; - std::array mBindingUniformInfo; CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; + utils::Invocable + mLogger; + SpecializationConstantsInfo mSpecializationConstants; + std::array, SHADER_TYPE_COUNT> mPushConstants; + DescriptorSetInfo mDescriptorBindings; + + // Descriptions for descriptor set layouts that may be used for this Program, which + // can be useful for attempting to compile the pipeline ahead of time. + DescriptorSetLayoutArray mDescriptorLayouts; + + // For ES2 support only + AttributesInfo mAttributes; + BindingUniformsInfo mBindingUniformsInfo; + // Indicates the current engine was initialized with multiview stereo, and the variant for this // program contains STE flag. This will be referred later for the OpenGL shader compiler to // determine whether shader code replacement for the num_views should be performed. diff --git a/package/android/libs/filament/include/backend/TargetBufferInfo.h b/package/android/libs/filament/include/backend/TargetBufferInfo.h index c4d284cc..c2538161 100644 --- a/package/android/libs/filament/include/backend/TargetBufferInfo.h +++ b/package/android/libs/filament/include/backend/TargetBufferInfo.h @@ -19,26 +19,26 @@ #include -#include +#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { //! \privatesection struct TargetBufferInfo { // note: the parameters of this constructor are not in the order of this structure's fields - TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept - : handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) { - } - - TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer) noexcept - : handle(handle), level(level), layer(layer) { + TargetBufferInfo(Handle handle, uint8_t const level, uint16_t const layer) noexcept + : handle(std::move(handle)), level(level), layer(layer) { } - TargetBufferInfo(Handle handle, uint8_t level) noexcept + TargetBufferInfo(Handle handle, uint8_t const level) noexcept : handle(handle), level(level) { } @@ -51,14 +51,15 @@ struct TargetBufferInfo { // texture to be used as render target Handle handle; - // Starting layer index for multiview. This value is only used when the `layerCount` for the - // render target is greater than 1. - uint8_t baseViewIndex = 0; - // level to be used uint8_t level = 0; - // For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping + // - For cubemap textures, this indicates the face of the cubemap. See TextureCubemapFace for + // the face->layer mapping) + // - For 2d array, cubemap array, and 3d textures, this indicates an index of a single layer of + // them. + // - For multiview textures (i.e., layerCount for the RenderTarget is greater than 1), this + // indicates a starting layer index of the current 2d array texture for multiview. uint16_t layer = 0; }; @@ -73,11 +74,11 @@ class MRT { TargetBufferInfo mInfos[MAX_SUPPORTED_RENDER_TARGET_COUNT]; public: - TargetBufferInfo const& operator[](size_t i) const noexcept { + TargetBufferInfo const& operator[](size_t const i) const noexcept { return mInfos[i]; } - TargetBufferInfo& operator[](size_t i) noexcept { + TargetBufferInfo& operator[](size_t const i) noexcept { return mInfos[i]; } @@ -103,7 +104,7 @@ class MRT { // this is here for backward compatibility MRT(Handle handle, uint8_t level, uint16_t layer) noexcept - : mInfos{{ handle, level, layer, 0 }} { + : mInfos{{ handle, level, layer }} { } }; diff --git a/package/android/libs/filament/include/backend/platforms/AndroidNdk.h b/package/android/libs/filament/include/backend/platforms/AndroidNdk.h new file mode 100644 index 00000000..a3766710 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/AndroidNdk.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILAMENT_BACKEND_ANDROIDNDK_H +#define FILAMENT_BACKEND_ANDROIDNDK_H + +#include +#include + +#define FILAMENT_REQUIRES_API(x) __attribute__((__availability__(android,introduced=x))) + +#define FILAMENT_USE_DLSYM(api) (__ANDROID_API__ < (api)) + +namespace filament::backend { + +class AndroidNdk { +public: + AndroidNdk(); + +#if FILAMENT_USE_DLSYM(26) + + static void AHardwareBuffer_acquire( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ndk.AHardwareBuffer_acquire(buffer); + } + + static void AHardwareBuffer_release( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ndk.AHardwareBuffer_release(buffer); + } + + static void AHardwareBuffer_describe( + AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) { + ndk.AHardwareBuffer_describe(buffer, desc); + } + +#else + + static void AHardwareBuffer_acquire( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ::AHardwareBuffer_acquire(buffer); + } + static void AHardwareBuffer_release( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ::AHardwareBuffer_release(buffer); + } + static void AHardwareBuffer_describe( + AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) { + ::AHardwareBuffer_describe(buffer, desc); + } + +#endif + +private: +#if FILAMENT_USE_DLSYM(26) + static struct Ndk { + void (*AHardwareBuffer_acquire)(AHardwareBuffer*); + void (*AHardwareBuffer_release)(AHardwareBuffer*); + void (*AHardwareBuffer_describe)(AHardwareBuffer const*, AHardwareBuffer_Desc*); + } ndk; +#endif +}; + +} // filament::backend + +#endif //FILAMENT_BACKEND_ANDROIDNDK_H diff --git a/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h b/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h index e00930c9..d71548e3 100644 --- a/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h +++ b/package/android/libs/filament/include/backend/platforms/OpenGLPlatform.h @@ -23,9 +23,11 @@ #include #include +#include #include #include +#include namespace filament::backend { @@ -51,12 +53,23 @@ class OpenGLPlatform : public Platform { ~OpenGLPlatform() noexcept override; public: - struct ExternalTexture { - unsigned int target; // GLenum target - unsigned int id; // GLuint id + unsigned int target; // GLenum target + unsigned int id; // GLuint id }; + /** + * Return the OpenGL vendor string of the specified Driver instance. + * @return The GL_VENDOR string + */ + static utils::CString getVendorString(Driver const* UTILS_NONNULL driver); + + /** + * Return the OpenGL vendor string of the specified Driver instance + * @return The GL_RENDERER string + */ + static utils::CString getRendererString(Driver const* UTILS_NONNULL driver); + /** * Called by the driver to destroy the OpenGL context. This should clean up any windows * or buffers from initialization. This is for instance where `eglDestroyContext` would be @@ -72,6 +85,15 @@ class OpenGLPlatform : public Platform { */ virtual bool isSRGBSwapChainSupported() const noexcept; + /** + * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_MSAA_*_SAMPLES flag. + * The default implementation returns false. + * + * @param samples The number of samples + * @return true if SWAP_CHAIN_CONFIG_MSAA_*_SAMPLES is supported, false otherwise. + */ + virtual bool isMSAASwapChainSupported(uint32_t samples) const noexcept; + /** * Return whether protected contexts are supported by this backend. * If protected context are supported, the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag can be @@ -90,7 +112,7 @@ class OpenGLPlatform : public Platform { * */ virtual SwapChain* UTILS_NULLABLE createSwapChain( - void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0; + void* UTILS_NULLABLE nativeWindow, uint64_t flags) = 0; /** * Called by the driver create a headless SwapChain. @@ -104,7 +126,7 @@ class OpenGLPlatform : public Platform { * A void* might be enough. */ virtual SwapChain* UTILS_NULLABLE createSwapChain( - uint32_t width, uint32_t height, uint64_t flags) noexcept = 0; + uint32_t width, uint32_t height, uint64_t flags) = 0; /** * Called by the driver to destroys the SwapChain @@ -186,7 +208,7 @@ class OpenGLPlatform : public Platform { */ virtual bool makeCurrent(ContextType type, SwapChain* UTILS_NONNULL drawSwapChain, - SwapChain* UTILS_NONNULL readSwapChain) noexcept = 0; + SwapChain* UTILS_NONNULL readSwapChain) = 0; /** * Called by the driver to make the OpenGL context active on the calling thread and bind @@ -206,7 +228,7 @@ class OpenGLPlatform : public Platform { SwapChain* UTILS_NONNULL drawSwapChain, SwapChain* UTILS_NONNULL readSwapChain, utils::Invocable preContextChange, - utils::Invocable postContextChange) noexcept; + utils::Invocable postContextChange); /** * Called by the backend just before calling commit() @@ -263,6 +285,26 @@ class OpenGLPlatform : public Platform { */ virtual backend::FenceStatus waitFence(Fence* UTILS_NONNULL fence, uint64_t timeout) noexcept; + // -------------------------------------------------------------------------------------------- + // Sync support + + /** + * Creates a Sync. These can be used for frame synchronization externally + * (certain platform implementations can be exported to handles that can + * be used in other processes). + * + * @return A Sync object. + */ + virtual Platform::Sync* UTILS_NONNULL createSync() noexcept; + + /** + * Destroys a sync. If called with a sync not created by this platform + * object, this will lead to undefined behavior. + * + * @param sync The sync to destroy, that was created by this platform + * instance. + */ + virtual void destroySync(Platform::Sync* UTILS_NONNULL sync) noexcept; // -------------------------------------------------------------------------------------------- // Streaming support @@ -307,6 +349,13 @@ class OpenGLPlatform : public Platform { virtual void updateTexImage(Stream* UTILS_NONNULL stream, int64_t* UTILS_NONNULL timestamp) noexcept; + /** + * Returns the transform matrix of the texture attached to the stream. + * @param stream Stream to get the transform matrix from + * @param uvTransform Output parameter: Transform matrix of the image bound to the texture. Returns identity if not supported. + */ + virtual math::mat3f getTransformMatrix(Stream* UTILS_NONNULL stream) noexcept; + // -------------------------------------------------------------------------------------------- // External Image support @@ -324,36 +373,45 @@ class OpenGLPlatform : public Platform { * Destroys an external texture handle and associated data. * @param texture a pointer to the handle to destroy. */ - virtual void destroyExternalImage(ExternalTexture* UTILS_NONNULL texture) noexcept; + virtual void destroyExternalImageTexture(ExternalTexture* UTILS_NONNULL texture) noexcept; // called on the application thread to allow Filament to take ownership of the image /** * Takes ownership of the externalImage. The externalImage parameter depends on the Platform's - * concrete implementation. Ownership is released when destroyExternalImage() is called. + * concrete implementation. Ownership is released when destroyExternalImageTexture() is called. * * WARNING: This is called synchronously from the application thread (NOT the Driver thread) * * @param externalImage A token representing the platform's external image. * @see destroyExternalImage + * @{ */ virtual void retainExternalImage(void* UTILS_NONNULL externalImage) noexcept; + virtual void retainExternalImage(ExternalImageHandleRef externalImage) noexcept; + /** @}*/ + /** * Called to bind the platform-specific externalImage to an ExternalTexture. * ExternalTexture::id is guaranteed to be bound when this method is called and ExternalTexture * is updated with new values for id/target if necessary. * * WARNING: this method is not allowed to change the bound texture, or must restore the previous - * binding upon return. This is to avoid problem with a backend doing state caching. + * binding upon return. This is to avoid a problem with a backend doing state caching. * * @param externalImage The platform-specific external image. * @param texture an in/out pointer to ExternalTexture, id and target can be updated if necessary. * @return true on success, false on error. + * @{ */ virtual bool setExternalImage(void* UTILS_NONNULL externalImage, ExternalTexture* UTILS_NONNULL texture) noexcept; + virtual bool setExternalImage(ExternalImageHandleRef externalImage, + ExternalTexture* UTILS_NONNULL texture) noexcept; + /** @}*/ + /** * The method allows platforms to convert a user-supplied external image object into a new type * (e.g. HardwareBuffer => EGLImage). The default implementation returns source. @@ -372,7 +430,7 @@ class OpenGLPlatform : public Platform { virtual bool isExtraContextSupported() const noexcept; /** - * Creates an OpenGL context with the same configuration than the main context and makes it + * Creates an OpenGL context with the same configuration as the main context and makes it * current to the current thread. Must not be called from the main driver thread. * createContext() is only supported if isExtraContextSupported() returns true. * These additional contexts will be automatically terminated in terminate. @@ -385,7 +443,7 @@ class OpenGLPlatform : public Platform { /** * Detach and destroy the current context if any and releases all resources associated to - * this thread. + * this thread. This must be called from the same thread where createContext() was called. */ virtual void releaseContext() noexcept; }; diff --git a/package/android/libs/filament/include/backend/platforms/PlatformCocoaGL.h b/package/android/libs/filament/include/backend/platforms/PlatformCocoaGL.h index 97c9c3ce..c424ec4f 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformCocoaGL.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformCocoaGL.h @@ -17,7 +17,6 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H -#include #include #include @@ -34,12 +33,14 @@ class PlatformCocoaGL : public OpenGLPlatform { PlatformCocoaGL(); ~PlatformCocoaGL() noexcept override; + ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept; + protected: // -------------------------------------------------------------------------------------------- // Platform Interface Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; // Currently returns 0 int getOSVersion() const noexcept override; @@ -57,12 +58,14 @@ class PlatformCocoaGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; - OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; - void destroyExternalImage(ExternalTexture* texture) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; void retainExternalImage(void* externalImage) noexcept override; bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept override; + void retainExternalImage(ExternalImageHandleRef externalImage) noexcept override; + bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override; private: PlatformCocoaGLImpl* pImpl = nullptr; diff --git a/package/android/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h b/package/android/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h index e7f1d1ff..94ca86d1 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h @@ -32,11 +32,13 @@ class PlatformCocoaTouchGL : public OpenGLPlatform { PlatformCocoaTouchGL(); ~PlatformCocoaTouchGL() noexcept override; + ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept; + // -------------------------------------------------------------------------------------------- // Platform Interface Driver* createDriver(void* sharedGLContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; int getOSVersion() const noexcept final { return 0; } @@ -53,13 +55,15 @@ class PlatformCocoaTouchGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; - OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; - void destroyExternalImage(ExternalTexture* texture) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; void retainExternalImage(void* externalImage) noexcept override; bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept override; + void retainExternalImage(ExternalImageHandleRef externalImage) noexcept override; + bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override; private: PlatformCocoaTouchGLImpl* pImpl = nullptr; diff --git a/package/android/libs/filament/include/backend/platforms/PlatformEGL.h b/package/android/libs/filament/include/backend/platforms/PlatformEGL.h index ef687653..8b8f2a95 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformEGL.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformEGL.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -47,6 +48,11 @@ class PlatformEGL : public OpenGLPlatform { // Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default. virtual bool isOpenGL() const noexcept; + /** + * Creates an ExternalImage from a EGLImageKHR + */ + ExternalImageHandle UTILS_PUBLIC createExternalImage(EGLImageKHR eglImage) noexcept; + protected: // -------------------------------------------------------------------------------------------- // Helper for EGL configs and attributes parameters @@ -75,8 +81,7 @@ class PlatformEGL : public OpenGLPlatform { * Initializes EGL, creates the OpenGL context and returns a concrete Driver implementation * that supports OpenGL/OpenGL ES. */ - Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + Driver* createDriver(void* sharedContext, const DriverConfig& driverConfig) override; /** * This returns zero. This method can be overridden to return something more useful. @@ -94,22 +99,19 @@ class PlatformEGL : public OpenGLPlatform { void terminate() noexcept override; bool isProtectedContextSupported() const noexcept override; - bool isSRGBSwapChainSupported() const noexcept override; - SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; - SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; - void destroySwapChain(SwapChain* swapChain) noexcept override; + bool isMSAASwapChainSupported(uint32_t samples) const noexcept override; bool isSwapChainProtected(SwapChain* swapChain) noexcept override; ContextType getCurrentContextType() const noexcept override; bool makeCurrent(ContextType type, SwapChain* drawSwapChain, - SwapChain* readSwapChain) noexcept override; + SwapChain* readSwapChain) override; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain, utils::Invocable preContextChange, - utils::Invocable postContextChange) noexcept override; + utils::Invocable postContextChange) override; void commit(SwapChain* swapChain) noexcept override; @@ -118,12 +120,13 @@ class PlatformEGL : public OpenGLPlatform { void destroyFence(Fence* fence) noexcept override; FenceStatus waitFence(Fence* fence, uint64_t timeout) noexcept override; - OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; - void destroyExternalImage(ExternalTexture* texture) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept override; + bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override; /** - * Logs glGetError() to slog.e + * Logs glGetError() to LOG(ERROR) * @param name a string giving some context on the error. Typically __func__. */ static void logEglError(const char* name) noexcept; @@ -143,25 +146,18 @@ class PlatformEGL : public OpenGLPlatform { EGLContext getContextForType(ContextType type) const noexcept; // makes the draw and read surface current without changing the current context - EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { + EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { return egl.makeCurrent(drawSurface, readSurface); } // makes context current and set draw and read surfaces to EGL_NO_SURFACE - EGLBoolean makeCurrent(EGLContext context) noexcept { + EGLBoolean makeCurrent(EGLContext context) { return egl.makeCurrent(context, mEGLDummySurface, mEGLDummySurface); } - // TODO: this should probably use getters instead. - EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; - EGLContext mEGLContext = EGL_NO_CONTEXT; - EGLContext mEGLContextProtected = EGL_NO_CONTEXT; - EGLSurface mEGLDummySurface = EGL_NO_SURFACE; - ContextType mCurrentContextType = ContextType::NONE; - // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false - EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; - Config mContextAttribs; - std::vector mAdditionalContexts; + EGLDisplay getEglDisplay() const noexcept { return mEGLDisplay; } + EGLConfig getEglConfig() const noexcept { return mEGLConfig; } + EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const; // supported extensions detected at runtime struct { @@ -178,7 +174,11 @@ class PlatformEGL : public OpenGLPlatform { } egl; } ext; - struct SwapChainEGL : public Platform::SwapChain { + struct SwapChainEGL : public SwapChain { + SwapChainEGL(PlatformEGL const& platform, void* nativeWindow, uint64_t flags); + SwapChainEGL(PlatformEGL const& platform, uint32_t width, uint32_t height, uint64_t flags); + void terminate(PlatformEGL& platform); + EGLSurface sur = EGL_NO_SURFACE; Config attribs{}; EGLNativeWindowType nativeWindow{}; @@ -188,10 +188,31 @@ class PlatformEGL : public OpenGLPlatform { void initializeGlExtensions() noexcept; -protected: - EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; + struct ExternalImageEGL : public ExternalImage { + EGLImageKHR eglImage = EGL_NO_IMAGE; + protected: + ~ExternalImageEGL() override; + }; private: + // prevent derived classes' implementations to call through + [[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override; + [[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + + EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; + + EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; + EGLContext mEGLContext = EGL_NO_CONTEXT; + EGLContext mEGLContextProtected = EGL_NO_CONTEXT; + EGLSurface mEGLDummySurface = EGL_NO_SURFACE; + ContextType mCurrentContextType = ContextType::NONE; + // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false + EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; + Config mContextAttribs; + std::vector mAdditionalContexts; + bool mMSAA4XSupport = false; + class EGL { EGLDisplay& mEGLDisplay; EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE; @@ -200,12 +221,14 @@ class PlatformEGL : public OpenGLPlatform { public: explicit EGL(EGLDisplay& dpy) : mEGLDisplay(dpy) {} EGLBoolean makeCurrent(EGLContext context, - EGLSurface drawSurface, EGLSurface readSurface) noexcept; + EGLSurface drawSurface, EGLSurface readSurface); - EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { + EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { return makeCurrent(mCurrentContext, drawSurface, readSurface); } } egl{ mEGLDisplay }; + + bool checkIfMSAASwapChainSupported(uint32_t samples) const noexcept; }; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h b/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h index c3cc7da8..9410a681 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformEGLAndroid.h @@ -17,12 +17,18 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H +#include "AndroidNdk.h" + #include +#include #include #include #include #include +#include + +#include #include @@ -37,13 +43,45 @@ class ExternalStreamManagerAndroid; * A concrete implementation of OpenGLPlatform and subclass of PlatformEGL that supports * EGL on Android. It adds Android streaming functionality to PlatformEGL. */ -class PlatformEGLAndroid : public PlatformEGL { +class PlatformEGLAndroid : public PlatformEGL, public AndroidNdk { public: PlatformEGLAndroid() noexcept; ~PlatformEGLAndroid() noexcept override; + /** + * Creates an ExternalImage from a EGLImageKHR + */ + ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept; + + struct UTILS_PUBLIC ExternalImageDescAndroid { + uint32_t width; // Texture width + uint32_t height; // Texture height + TextureFormat format;// Texture format + TextureUsage usage; // Texture usage flags + }; + + ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(ExternalImageHandle externalImage) noexcept; + + /** + * Converts a sync to an external file descriptor, if possible. Accepts an + * opaque handle to a sync, as well as a pointer to where the fd should be + * stored. + * @param sync The sync to be converted to a file descriptor. + * @param fd A pointer to where the file descriptor should be stored. + * @return `true` on success, `false` on failure. The default implementation + * returns `false`. + */ + bool convertSyncToFd(Sync* sync, int* fd) noexcept; + protected: + struct { + struct { + bool ANDROID_presentation_time = false; + bool ANDROID_get_frame_timestamps = false; + bool ANDROID_native_fence_sync = false; + } egl; + } ext; // -------------------------------------------------------------------------------------------- // Platform Interface @@ -55,11 +93,25 @@ class PlatformEGLAndroid : public PlatformEGL { int getOSVersion() const noexcept override; Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; + + bool isCompositorTimingSupported() const noexcept override; + + bool queryCompositorTiming(SwapChain const* swapchain, + CompositorTiming* outCompositorTiming) const noexcept override; + + bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override; + + bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId, + FrameTimestamps* outFrameTimestamps) const noexcept override; // -------------------------------------------------------------------------------------------- // OpenGLPlatform Interface + struct SyncEGLAndroid : public Sync { + EGLSyncKHR sync; + }; + void terminate() noexcept override; void beginFrame( @@ -75,12 +127,14 @@ class PlatformEGLAndroid : public PlatformEGL { */ void setPresentationTime(int64_t presentationTimeInNanosecond) noexcept override; - Stream* createStream(void* nativeStream) noexcept override; void destroyStream(Stream* stream) noexcept override; + Sync* createSync() noexcept override; + void destroySync(Sync* sync) noexcept override; void attach(Stream* stream, intptr_t tname) noexcept override; void detach(Stream* stream) noexcept override; void updateTexImage(Stream* stream, int64_t* timestamp) noexcept override; + math::mat3f getTransformMatrix(Stream* stream) noexcept override; /** * Converts a AHardwareBuffer to EGLImage @@ -89,19 +143,61 @@ class PlatformEGLAndroid : public PlatformEGL { */ AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; + + struct ExternalImageEGLAndroid : public ExternalImageEGL { + AHardwareBuffer* aHardwareBuffer = nullptr; + uint32_t width; // Texture width + uint32_t height; // Texture height + TextureFormat format;// Texture format + TextureUsage usage; // Texture usage flags + bool sRGB = false; + + protected: + ~ExternalImageEGLAndroid() override; + }; + + bool setExternalImage(ExternalImageHandleRef externalImage, + ExternalTexture* texture) noexcept override; + bool setImage(ExternalImageEGLAndroid const* eglExternalImage, + ExternalTexture* texture) noexcept; + + bool makeCurrent(ContextType type, + SwapChain* drawSwapChain, + SwapChain* readSwapChain) override; + private: + struct SwapChainEGLAndroid; + struct AndroidDetails; + + // prevent derived classes' implementations to call through + [[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override; + [[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + + bool isProducerThrottlingControlSupported() const; + + int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const; + struct InitializeJvmForPerformanceManagerIfNeeded { InitializeJvmForPerformanceManagerIfNeeded(); }; + struct ExternalTextureAndroid : public ExternalTexture { + EGLImageKHR eglImage = EGL_NO_IMAGE; + }; + int mOSVersion; ExternalStreamManagerAndroid& mExternalStreamManager; + AndroidDetails& mAndroidDetails; InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded; utils::PerformanceHintManager mPerformanceHintManager; utils::PerformanceHintManager::Session mPerformanceHintSession; - using clock = std::chrono::high_resolution_clock; clock::time_point mStartTimeOfActualWork; + SwapChainEGLAndroid* mCurrentDrawSwapChain{}; + bool mAssertNativeWindowIsValid = false; }; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/platforms/PlatformEGLHeadless.h b/package/android/libs/filament/include/backend/platforms/PlatformEGLHeadless.h index 40d285b9..300748e0 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformEGLHeadless.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformEGLHeadless.h @@ -29,7 +29,7 @@ class PlatformEGLHeadless : public PlatformEGL { PlatformEGLHeadless() noexcept; Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const Platform::DriverConfig& driverConfig) override; protected: bool isOpenGL() const noexcept override; diff --git a/package/android/libs/filament/include/backend/platforms/PlatformGLX.h b/package/android/libs/filament/include/backend/platforms/PlatformGLX.h index 796e27a1..8d0eda33 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformGLX.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformGLX.h @@ -26,7 +26,10 @@ #include +#include +#include #include +#include namespace filament::backend { @@ -39,7 +42,7 @@ class PlatformGLX : public OpenGLPlatform { // Platform Interface Driver* createDriver(void* sharedGLContext, - const DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; int getOSVersion() const noexcept final override { return 0; } @@ -48,18 +51,26 @@ class PlatformGLX : public OpenGLPlatform { void terminate() noexcept override; + bool isExtraContextSupported() const noexcept override; + void createContext(bool shared) override; + void releaseContext() noexcept override; + SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; private: - Display *mGLXDisplay; - GLXContext mGLXContext; - GLXFBConfig* mGLXConfig; + Display* mGLXDisplay; + GLXContext mGLXContext{}; + GLXFBConfig mGLXConfig{}; GLXPbuffer mDummySurface; std::vector mPBuffers; + + // Variables for shared contexts + std::unordered_map mAdditionalContexts; + std::shared_mutex mAdditionalContextsLock; }; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h b/package/android/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h new file mode 100644 index 00000000..2635ea67 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_OBJC_H +#define TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_OBJC_H + +#import + +namespace filament::backend { + +struct MetalDevice { + id device; +}; + +struct MetalCommandQueue { + id commandQueue; +}; + +struct MetalCommandBuffer { + id commandBuffer; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_OBJC_H diff --git a/package/android/libs/filament/include/backend/platforms/PlatformMetal.h b/package/android/libs/filament/include/backend/platforms/PlatformMetal.h new file mode 100644 index 00000000..a2e3ac25 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/PlatformMetal.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H +#define TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H + +#include +#include + +namespace filament::backend { + +struct PlatformMetalImpl; + +// In order for this header to be compatible with Objective-C and C++, we use these wrappers around +// id objects. +// See PlatformMetal-Objc.h. +struct MetalDevice; +struct MetalCommandQueue; +struct MetalCommandBuffer; + +class PlatformMetal final : public Platform { +public: + PlatformMetal(); + ~PlatformMetal() noexcept override; + + Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override; + int getOSVersion() const noexcept override { return 0; } + + /** + * Optionally initializes the Metal platform by acquiring resources necessary for rendering. + * + * This method attempts to acquire a Metal device and command queue, returning true if both are + * successfully obtained, or false otherwise. Typically, these objects are acquired when + * the Metal backend is initialized. This method allows clients to check for their availability + * earlier. + * + * Calling initialize() is optional and safe to do so multiple times. After initialize() returns + * true, subsequent calls will continue to return true but have no effect. + * + * initialize() must be called from the main thread. + * + * @returns true if the device and command queue have been successfully obtained; false + * otherwise. + */ + bool initialize() noexcept; + + /** + * Obtain the preferred Metal device object for the backend to use. + * + * On desktop platforms, there may be multiple GPUs suitable for rendering, and this method is + * free to decide which one to use. On mobile systems with a single GPU, implementations should + * simply return the result of MTLCreateSystemDefaultDevice(); + * + * createDevice is called by the Metal backend from the backend thread. + */ + virtual void createDevice(MetalDevice& outDevice) noexcept; + + /** + * Create a command submission queue on the Metal device object. + * + * createCommandQueue is called by the Metal backend from the backend thread. + * + * @param device The device which was returned from createDevice() + */ + virtual void createCommandQueue( + MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept; + + /** + * Obtain a MTLCommandBuffer enqueued on this Platform's MTLCommandQueue. The command buffer is + * guaranteed to execute before all subsequent command buffers created either by Filament, or + * further calls to this method. + * + * createAndEnqueueCommandBuffer must be called from the main thread. + */ + void createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept; + + /** + * The action to take if a Drawable cannot be acquired. + * + * Each frame rendered requires a CAMetalDrawable texture, which is presented on-screen at the + * completion of each frame. These are limited and provided round-robin style by the system. + * + * setDrawableFailureBehavior must be called from the main thread. + */ + enum class DrawableFailureBehavior : uint8_t { + /** + * Terminates the application and reports an error message (default). + */ + PANIC, + /* + * Aborts execution of the current frame. The Metal backend will attempt to acquire a new + * drawable at the next frame. + */ + ABORT_FRAME + }; + void setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept; + DrawableFailureBehavior getDrawableFailureBehavior() const noexcept; + +private: + PlatformMetalImpl* pImpl = nullptr; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H diff --git a/package/android/libs/filament/include/backend/platforms/PlatformOSMesa.h b/package/android/libs/filament/include/backend/platforms/PlatformOSMesa.h new file mode 100644 index 00000000..10e1fb18 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/PlatformOSMesa.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H +#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H + +#include + +#include "bluegl/BlueGL.h" + +#if defined(__linux__) +#include +#elif defined(__APPLE__) +#undef GLAPI +#include +#endif + +#include +#include + +namespace filament::backend { + +/** + * A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen + * context that can be used in conjunction with Mesa for software rasterization. + * See https://docs.mesa3d.org/osmesa.html for more information. + */ +class PlatformOSMesa : public OpenGLPlatform { +protected: + // -------------------------------------------------------------------------------------------- + // Platform Interface + + Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) override; + + int getOSVersion() const noexcept final override { return 0; } + + // -------------------------------------------------------------------------------------------- + // OpenGLPlatform Interface + + void terminate() noexcept override; + + SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) override; + void commit(SwapChain* swapChain) noexcept override; + +private: + OSMesaContext mContext; + void* mOsMesaApi = nullptr; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H diff --git a/package/android/libs/filament/include/backend/platforms/PlatformWGL.h b/package/android/libs/filament/include/backend/platforms/PlatformWGL.h index e0003156..49c79812 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformWGL.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformWGL.h @@ -38,7 +38,7 @@ class PlatformWGL : public OpenGLPlatform { // Platform Interface Driver* createDriver(void* sharedGLContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const Platform::DriverConfig& driverConfig) override; int getOSVersion() const noexcept final override { return 0; } @@ -53,7 +53,7 @@ class PlatformWGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; protected: @@ -61,8 +61,12 @@ class PlatformWGL : public OpenGLPlatform { HWND mHWnd = NULL; HDC mWhdc = NULL; PIXELFORMATDESCRIPTOR mPfd = {}; - std::vector mAdditionalContexts; std::vector mAttribs; + + // For shared contexts + static constexpr int SHARED_CONTEXT_NUM = 2; + std::vector mAdditionalContexts; + std::atomic mNextFreeSharedContextIndex{0}; }; } // namespace filament::backend diff --git a/package/android/libs/filament/include/backend/platforms/PlatformWebGL.h b/package/android/libs/filament/include/backend/platforms/PlatformWebGL.h index 0d83fbb9..21083f58 100644 --- a/package/android/libs/filament/include/backend/platforms/PlatformWebGL.h +++ b/package/android/libs/filament/include/backend/platforms/PlatformWebGL.h @@ -33,8 +33,7 @@ class PlatformWebGL : public OpenGLPlatform { // -------------------------------------------------------------------------------------------- // Platform Interface - Driver* createDriver(void* sharedGLContext, - const Platform::DriverConfig& driverConfig) noexcept override; + Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) override; int getOSVersion() const noexcept override; @@ -43,10 +42,10 @@ class PlatformWebGL : public OpenGLPlatform { void terminate() noexcept override; - SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; + SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; }; diff --git a/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h b/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h index 2bbe1983..edfdea29 100644 --- a/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h +++ b/package/android/libs/filament/include/backend/platforms/VulkanPlatform.h @@ -17,6 +17,8 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H +#include +#include #include #include @@ -26,6 +28,9 @@ #include #include +#include +#include +#include #include #include @@ -41,6 +46,10 @@ using SwapChain = Platform::SwapChain; */ struct VulkanPlatformPrivate; +// Forward declare the fence status that will be maintained by the command +// buffer manager. +struct VulkanCmdFence; + /** * A Platform interface that creates a Vulkan backend. */ @@ -71,6 +80,9 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation explicitImageReadyWait = nullptr; }; VulkanPlatform(); @@ -109,7 +119,7 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation fenceStatus) noexcept; + + /** + * Destroys a sync. If called with a sync not created by this platform + * object, this will lead to undefined behavior. + * + * @param sync The sync to destroy, which was created by this platform + * instance. + */ + virtual void destroySync(Platform::Sync* sync) noexcept; + /** * Allows implementers to provide instance extensions that they'd like to include in the * instance creation. @@ -267,13 +303,226 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation fenceStatus; + }; + + /** + * Creates the VkInstance used by Filament's Vulkan backend. + * + * This method can be overridden in subclasses to customize VkInstance creation, such as + * adding application-specific layers or extensions. + * + * The provided `createInfo` contains layers and extensions required by Filament. + * If you override this method and need to modify the `createInfo` struct, you must first + * make a copy of it and modify the copy. + * + * @param createInfo The VkInstanceCreateInfo prepared by Filament. + * @return The created VkInstance, or VK_NULL_HANDLE on failure. + */ + virtual VkInstance createVkInstance(VkInstanceCreateInfo const& createInfo) noexcept; + + /** + * Selects a VkPhysicalDevice (GPU) for Filament's Vulkan backend to use. + * + * This method can be overridden in subclasses to implement custom GPU selection logic. + * For example, an application might override this to prefer a discrete GPU over an + * integrated one based on device properties. + * + * The default implementation selects the first device that meets Filament's requirements. + * + * @param instance The VkInstance to enumerate devices from. + * @return The selected VkPhysicalDevice, or VK_NULL_HANDLE if no suitable device is found. + */ + virtual VkPhysicalDevice selectVkPhysicalDevice(VkInstance instance) noexcept; + + /** + * Creates the VkDevice used by Filament's Vulkan backend. + * + * This method can be overridden in subclasses to customize VkDevice creation, such as + * adding application-specific extensions or enabling features. + * + * The provided `createInfo` contains extensions and features required by Filament. + * If you override this method and need to modify the `createInfo` struct, you must first + * make a copy of it and modify the copy. + * + * @param createInfo The VkDeviceCreateInfo prepared by Filament. + * @return The created VkDevice, or VK_NULL_HANDLE on failure. + */ + virtual VkDevice createVkDevice(VkDeviceCreateInfo const& createInfo) noexcept; - // Platform dependent helper methods using SurfaceBundle = std::tuple; - static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, - uint64_t flags) noexcept; + virtual ExtensionSet getSwapchainInstanceExtensions() const = 0; + virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept = 0; + + virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept; + + /** + * Query if transient attachments are supported by the backend. + */ + bool isTransientAttachmentSupported() const noexcept; + +private: + /** + * Contains information about features that should be requested + * when calling vkCreateDevice, based on feature support from + * vkGetPhysicalDeviceFeatures2. + */ + struct MiscDeviceFeatures { + /** + * This allows creation of a VkGraphicsPipeline without a + * render pass specified. + */ + bool dynamicRendering; + + /** + * Allows creation of a 2d image view, or 2d image view array, + * to be created from a 3d VkImage. + */ + bool imageView2Don3DImage; + }; + + void createInstance(ExtensionSet const& requiredExts) noexcept; + + void queryAndSetDeviceFeatures(Platform::DriverConfig const& driverConfig, + ExtensionSet const& instExts, ExtensionSet const& deviceExts, + void* sharedContext) noexcept; + + void createLogicalDeviceAndQueues(ExtensionSet const& deviceExtensions, + VkPhysicalDeviceFeatures const& features, + VkPhysicalDeviceVulkan11Features const& vk11Features, bool createProtectedQueue, + MiscDeviceFeatures const& requestedFeatures) noexcept; friend struct VulkanPlatformPrivate; }; diff --git a/package/android/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h b/package/android/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h new file mode 100644 index 00000000..ba82db81 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H + +#include "AndroidNdk.h" + +#include +#include + +#include + +#include + +namespace filament::backend { + +class VulkanPlatformAndroid : public VulkanPlatform, public AndroidNdk { +public: + ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, + bool sRGB) noexcept; + + struct UTILS_PUBLIC ExternalImageDescAndroid { + uint32_t width; // Texture width + uint32_t height; // Texture height + TextureFormat format;// Texture format + TextureUsage usage; // Texture usage flags + }; + + VulkanPlatformAndroid(); + + ~VulkanPlatformAndroid() noexcept override; + + ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc( + ExternalImageHandleRef externalImage) const noexcept; + + ExternalImageMetadata extractExternalImageMetadata( + ExternalImageHandleRef image) const override; + + ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override; + + /** + * Converts a sync to an external file descriptor, if possible. Accepts an + * opaque handle to a sync, as well as a pointer to where the fd should be + * stored. + * @param sync The sync to be converted to a file descriptor. + * @param fd A pointer to where the file descriptor should be stored. + * @return `true` on success, `false` on failure. The default implementation + * returns `false`. + */ + bool convertSyncToFd(Sync* sync, int* fd) const noexcept; + + int getOSVersion() const noexcept override; + + void terminate() override; + + Driver* createDriver(void* sharedContext, + DriverConfig const& driverConfig) override; + + + bool isCompositorTimingSupported() const noexcept override; + + bool queryCompositorTiming(SwapChain const* swapchain, + CompositorTiming* outCompositorTiming) const noexcept override; + + bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override; + + bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId, + FrameTimestamps* outFrameTimestamps) const noexcept override; + + +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + + using SurfaceBundle = SurfaceBundle; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; + + VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override; + +private: + struct AndroidDetails; + + struct ExternalImageVulkanAndroid : public ExternalImage { + AHardwareBuffer* aHardwareBuffer = nullptr; + bool sRGB = false; + + protected: + ~ExternalImageVulkanAndroid() override; + }; + + AndroidDetails& mAndroidDetails; + int mOSVersion{}; +}; + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H diff --git a/package/android/libs/filament/include/backend/platforms/VulkanPlatformApple.h b/package/android/libs/filament/include/backend/platforms/VulkanPlatformApple.h new file mode 100644 index 00000000..5a7c2119 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/VulkanPlatformApple.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H + +#include + +namespace filament::backend { + +class VulkanPlatformApple : public VulkanPlatform { +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H diff --git a/package/android/libs/filament/include/backend/platforms/VulkanPlatformLinux.h b/package/android/libs/filament/include/backend/platforms/VulkanPlatformLinux.h new file mode 100644 index 00000000..2fb964dd --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/VulkanPlatformLinux.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H + +#include + +namespace filament::backend { + +class VulkanPlatformLinux : public VulkanPlatform { +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H diff --git a/package/android/libs/filament/include/backend/platforms/VulkanPlatformWindows.h b/package/android/libs/filament/include/backend/platforms/VulkanPlatformWindows.h new file mode 100644 index 00000000..0b55dede --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/VulkanPlatformWindows.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H + +#include + +namespace filament::backend { + +class VulkanPlatformWindows : public VulkanPlatform { +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H diff --git a/package/android/libs/filament/include/backend/platforms/WebGPUPlatform.h b/package/android/libs/filament/include/backend/platforms/WebGPUPlatform.h new file mode 100644 index 00000000..ebdb70ac --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/WebGPUPlatform.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H + +#include + +#if defined(__linux__) && defined(FILAMENT_SUPPORTS_X11) +// Resolve the conflicts between webgpu_cpp.h and X11 defines +#undef Always +#undef Success +#undef None +#undef True +#undef False +#undef Status +#undef Bool +#endif +#include + +#include +#include + +namespace filament::backend { + +/** + * A Platform interface, handling the environment-specific concerns, e.g. OS, for creating a WebGPU + * driver (backend). + */ +class WebGPUPlatform : public Platform { +public: + WebGPUPlatform(); + ~WebGPUPlatform() override = default; + + [[nodiscard]] int getOSVersion() const noexcept final { return 0; } + + [[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; } + + // TODO consider that this functionality is not WebGPU-specific, and thus could be + // placed in a generic place and even reused across backends. Alternatively, + // a 3rd party library could be considered. However, this was a simple and + // quick change and works for now. + // gets the size (height and width) of the surface/window + [[nodiscard]] virtual wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const = 0; + // either returns a valid surface or panics + [[nodiscard]] virtual wgpu::Surface createSurface(void* nativeWindow, uint64_t flags) = 0; + // either returns a valid adapter or panics + [[nodiscard]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface); + // either returns a valid device or panics + [[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter); + + struct Configuration { + wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined; + }; + + [[nodiscard]] virtual Configuration getConfiguration() const noexcept { + return {}; + } + +protected: + [[nodiscard]] Driver* createDriver(void* sharedContext, + const Platform::DriverConfig& driverConfig) override; + + // returns adapter request option variations applicable for the particular + // platform + [[nodiscard]] virtual std::vector getAdapterOptions() = 0; + + // we may consider having the driver own this in the future + wgpu::Instance mInstance; +}; + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H diff --git a/package/android/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h new file mode 100644 index 00000000..fc3f33b1 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h @@ -0,0 +1,35 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMANDROID_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMANDROID_H + +#include + +namespace filament::backend { + +class WebGPUPlatformAndroid : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMANDROID_H diff --git a/package/android/libs/filament/include/backend/platforms/WebGPUPlatformApple.h b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformApple.h new file mode 100644 index 00000000..81f9f089 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformApple.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMAPPLE_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMAPPLE_H + +#include + +namespace filament::backend { + +class WebGPUPlatformApple : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMAPPLE_H diff --git a/package/android/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h new file mode 100644 index 00000000..43ad7af6 --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h @@ -0,0 +1,35 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMLINUX_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMLINUX_H + +#include + +namespace filament::backend { + +class WebGPUPlatformLinux : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMLINUX_H diff --git a/package/android/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h new file mode 100644 index 00000000..cc7f6d9a --- /dev/null +++ b/package/android/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h @@ -0,0 +1,35 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWINDOWS_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWINDOWS_H + +#include + +namespace filament::backend { + +class WebGPUPlatformWindows : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWINDOWS_H diff --git a/package/android/libs/filament/include/camutils/Manipulator.h b/package/android/libs/filament/include/camutils/Manipulator.h index 2cc8a4da..5ea49eaf 100644 --- a/package/android/libs/filament/include/camutils/Manipulator.h +++ b/package/android/libs/filament/include/camutils/Manipulator.h @@ -109,6 +109,7 @@ class CAMUTILS_PUBLIC Manipulator { vec4 groundPlane; RayCallback raycastCallback; void* raycastUserdata; + bool panning = true; }; struct Builder { @@ -143,6 +144,8 @@ class CAMUTILS_PUBLIC Manipulator { Builder& groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d); //! Plane equation used as a raycast fallback Builder& raycastCallback(RayCallback cb, void* userdata); //! Raycast function for accurate grab-and-pan + Builder& panning(bool enabled); //! Sets whether panning is enabled + /** * Creates a new camera manipulator, either ORBIT, MAP, or FREE_FLIGHT. * diff --git a/package/android/libs/filament/include/filamat/Enums.h b/package/android/libs/filament/include/filamat/Enums.h index 04b5ff8b..3a28bc5b 100644 --- a/package/android/libs/filament/include/filamat/Enums.h +++ b/package/android/libs/filament/include/filamat/Enums.h @@ -35,6 +35,7 @@ using OutputTarget = MaterialBuilder::OutputTarget; using OutputQualifier = MaterialBuilder::VariableQualifier; using OutputType = MaterialBuilder::OutputType; using ConstantType = MaterialBuilder::ConstantType; +using ShaderStageType = MaterialBuilder::ShaderStageFlags; // Convenience methods to convert std::string to Enum and also iterate over Enum values. class Enums { @@ -79,6 +80,7 @@ class Enums { static std::unordered_map mStringToOutputQualifier; static std::unordered_map mStringToOutputType; static std::unordered_map mStringToConstantType; + static std::unordered_map mStringToShaderStageType; }; template diff --git a/package/android/libs/filament/include/filamat/IncludeCallback.h b/package/android/libs/filament/include/filamat/IncludeCallback.h deleted file mode 100644 index 659ba289..00000000 --- a/package/android/libs/filament/include/filamat/IncludeCallback.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_FILAMAT_INCLUDER_H -#define TNT_FILAMAT_INCLUDER_H - -#include - -#include - -namespace filamat { - -struct IncludeResult { - // The include name of the root file, as if it were being included. - // I.e., 'foobar.h' in the case of #include "foobar.h" - const utils::CString includeName; - - // The following fields should be filled out by the IncludeCallback when processing an include, - // or when calling resolveIncludes for the root file. - - // The full contents of the include file. This may contain additional, recursive include - // directives. - utils::CString text; - - // The line number for the first line of text (first line is 0). - size_t lineNumberOffset = 0; - - // The name of the include file. This gets passed as "includerName" for any includes inside of - // source. This field isn't used by the include system; it's up to the callback to give meaning - // to this value and interpret it accordingly. In the case of DirIncluder, this is an empty - // string to represent the root include file, and a canonical path for subsequent included - // files. - utils::CString name; -}; - -/** - * A callback invoked by the include system when an #include "file.h" directive is found. - * - * For example, if a file main.h includes file.h on line 10, then IncludeCallback would be called - * with the following: - * includeCallback("main.h", {.includeName = "file.h" }) - * It's then up to the IncludeCallback to fill out the .text, .name, and (optionally) - * lineNumberOffset fields. - * - * @param includedBy is the value that was given to IncludeResult.name for this source file, or - * the empty string for the root source file. - * @param result is the IncludeResult that the callback should fill out. - * @return true, if the include was resolved successfully, false otherwise. - * - * For an example of implementing this callback, see tools/matc/src/matc/DirIncluder.h. - */ -using IncludeCallback = std::function; - -} // namespace filamat - -#endif diff --git a/package/android/libs/filament/include/filamat/MaterialBuilder.h b/package/android/libs/filament/include/filamat/MaterialBuilder.h index c3045b94..e8b3ea78 100644 --- a/package/android/libs/filament/include/filamat/MaterialBuilder.h +++ b/package/android/libs/filament/include/filamat/MaterialBuilder.h @@ -21,7 +21,6 @@ #include -#include #include #include @@ -35,11 +34,14 @@ #include #include +#include +#include #include #include #include #include #include +#include #include #include @@ -80,7 +82,12 @@ class UTILS_PUBLIC MaterialBuilderBase { OPENGL = 0x01u, VULKAN = 0x02u, METAL = 0x04u, + WEBGPU = 0x08u, +#ifdef FILAMENT_SUPPORTS_WEBGPU + ALL = OPENGL | VULKAN | METAL | WEBGPU +#else ALL = OPENGL | VULKAN | METAL +#endif }; /* @@ -110,6 +117,11 @@ class UTILS_PUBLIC MaterialBuilderBase { PERFORMANCE }; + enum class Workarounds : uint64_t { + NONE = 0, + ALL = 0xFFFFFFFFFFFFFFFF + }; + /** * Initialize MaterialBuilder. * @@ -133,7 +145,9 @@ class UTILS_PUBLIC MaterialBuilderBase { Platform mPlatform = Platform::DESKTOP; TargetApi mTargetApi = (TargetApi) 0; Optimization mOptimization = Optimization::PERFORMANCE; + Workarounds mWorkarounds = Workarounds::ALL; bool mPrintShaders = false; + bool mSaveRawVariants = false; bool mGenerateDebugInfo = false; bool mIncludeEssl1 = true; utils::bitset32 mShaderModels; @@ -162,6 +176,7 @@ inline constexpr MaterialBuilderBase::TargetApi targetApiFromBackend( case Backend::OPENGL: return TargetApi::OPENGL; case Backend::VULKAN: return TargetApi::VULKAN; case Backend::METAL: return TargetApi::METAL; + case Backend::WEBGPU: return TargetApi::WEBGPU; case Backend::NOOP: return TargetApi::OPENGL; } } @@ -210,12 +225,13 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { MaterialBuilder(MaterialBuilder&& rhs) noexcept = default; MaterialBuilder& operator=(MaterialBuilder&& rhs) noexcept = default; - static constexpr size_t MATERIAL_VARIABLES_COUNT = 4; + static constexpr size_t MATERIAL_VARIABLES_COUNT = 5; enum class Variable : uint8_t { CUSTOM0, CUSTOM1, CUSTOM2, - CUSTOM3 + CUSTOM3, + CUSTOM4, // CUSTOM4 is only available if the vertex attribute `color` is not required. // when adding more variables, make sure to update MATERIAL_VARIABLES_COUNT }; @@ -237,6 +253,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using AttributeType = filament::backend::UniformType; using UniformType = filament::backend::UniformType; using ConstantType = filament::backend::ConstantType; + using ConstantValue = filament::backend::ConstantValue; using SamplerType = filament::backend::SamplerType; using SubpassType = filament::backend::SubpassType; using SamplerFormat = filament::backend::SamplerFormat; @@ -246,6 +263,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using FeatureLevel = filament::backend::FeatureLevel; using StereoscopicType = filament::backend::StereoscopicType; using ShaderStage = filament::backend::ShaderStage; + using ShaderStageFlags = filament::backend::ShaderStageFlags; enum class VariableQualifier : uint8_t { OUT @@ -284,6 +302,9 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Set the file name of this material file. Used in error reporting. MaterialBuilder& fileName(const char* name) noexcept; + //! Set the commandline parameters of matc. Used for debugging purpose. + MaterialBuilder& compilationParameters(const char* params) noexcept; + //! Set the shading model. MaterialBuilder& shading(Shading shading) noexcept; @@ -296,7 +317,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Add a parameter array to this material. MaterialBuilder& parameter(const char* name, size_t size, UniformType type, - ParameterPrecision precision = ParameterPrecision::DEFAULT) noexcept; + ParameterPrecision precision = ParameterPrecision::DEFAULT); //! Add a constant parameter to this material. template @@ -315,13 +336,18 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { MaterialBuilder& parameter(const char* name, SamplerType samplerType, SamplerFormat format = SamplerFormat::FLOAT, ParameterPrecision precision = ParameterPrecision::DEFAULT, - bool multisample = false) noexcept; + bool filterable = true, /* defaulting to filterable because format is default to float */ + bool multisample = false, const char* transformName = "", + std::optional stages = {}); - MaterialBuilder& buffer(filament::BufferInterfaceBlock bib) noexcept; + MaterialBuilder& buffer(filament::BufferInterfaceBlock bib); //! Custom variables (all float4). MaterialBuilder& variable(Variable v, const char* name) noexcept; + MaterialBuilder& variable(Variable v, const char* name, + ParameterPrecision precision) noexcept; + /** * Require a specified attribute. * @@ -357,18 +383,13 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { * } * ~~~~~ * - * @param code The source code of the material. + * @param code The source code of the material. Expected it to be all inlined. (#includes are + * resolved.) * @param line The line number offset of the material, where 0 is the first line. Used for error * reporting */ MaterialBuilder& material(const char* code, size_t line = 0) noexcept; - /** - * Set the callback used for resolving include directives. - * The default is no callback, which disallows all includes. - */ - MaterialBuilder& includeCallback(IncludeCallback callback) noexcept; - /** * Set the vertex code content of this material. * @@ -392,7 +413,8 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { * } * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * @param code The source code of the material. + * @param code The source code of the material. Expected it to be all inlined. (#includes are + * resolved.) * @param line The line number offset of the material, where 0 is the first line. Used for error * reporting */ @@ -516,6 +538,12 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Enable / disable flipping of the Y coordinate of UV attributes, enabled by default. MaterialBuilder& flipUV(bool flipUV) noexcept; + //! Enable / disable the cheapest linear fog, disabled by default. + MaterialBuilder& linearFog(bool enabled) noexcept; + + //! Enable / disable shadow far attenuation, enabled by default. + MaterialBuilder& shadowFarAttenuation(bool enabled) noexcept; + //! Enable / disable multi-bounce ambient occlusion, disabled by default on mobile. MaterialBuilder& multiBounceAmbientOcclusion(bool multiBounceAO) noexcept; @@ -581,11 +609,25 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { */ MaterialBuilder& optimization(Optimization optimization) noexcept; - // TODO: this is present here for matc's "--print" flag, but ideally does not belong inside - // MaterialBuilder. + /** + * Specifies workarounds to enable during code generation. By default, all workaround are + * enabled. These workarounds typically disable important optimizations and in some cases + * whole features. + */ + MaterialBuilder& workarounds(Workarounds workarounds) noexcept; + + // TODO: this is present here for matc's "--print" flag, but ideally does not belong inside MaterialBuilder. //! If true, will output the generated GLSL shader code to stdout. MaterialBuilder& printShaders(bool printShaders) noexcept; + /** + * If true, this will write the raw generated GLSL for each variant to a text file in the + * current directory. The file will be named after the material name and the variant name. Its + * extension will be derived from the shader stage. For example, mymaterial_0x0e.frag, + * mymaterial_0x18.vert, etc. + */ + MaterialBuilder& saveRawVariants(bool saveRawVariants) noexcept; + //! If true, will include debugging information in generated SPIRV. MaterialBuilder& generateDebugInfo(bool generateDebugInfo) noexcept; @@ -597,7 +639,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Add a new fragment shader output variable. Only valid for materials in the POST_PROCESS domain. MaterialBuilder& output(VariableQualifier qualifier, OutputTarget target, Precision precision, - OutputType type, const char* name, int location = -1) noexcept; + OutputType type, const char* name, int location = -1); MaterialBuilder& enableFramebufferFetch() noexcept; @@ -612,11 +654,26 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! specify compute kernel group size MaterialBuilder& groupSize(filament::math::uint3 groupSize) noexcept; + /** + * Force Filament to use its default variant for depth passes. Useful if a material provides a + * custom vertex shader which can be skipped during depth-only passes. + */ + MaterialBuilder& useDefaultDepthVariant() noexcept; + + /** + * Sets the source ASCII material (aka .mat file). + * The provided `source` string_view must remain valid until MaterialBuilder::build() is called. + */ + MaterialBuilder& materialSource(std::string_view source) noexcept; + + //! Set the (client requested) api level that the material is supposed to be compiled against. + MaterialBuilder& setApiLevel(uint32_t apiLevel) noexcept; + /** * Build the material. If you are using the Filament engine with this library, you should use * the job system provided by Engine. */ - Package build(utils::JobSystem& jobSystem) noexcept; + Package build(utils::JobSystem& jobSystem); public: // The methods and types below are for internal use @@ -626,27 +683,51 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { * Add a subpass parameter to this material. */ MaterialBuilder& subpass(SubpassType subpassType, - SamplerFormat format, ParameterPrecision precision, const char* name) noexcept; + SamplerFormat format, ParameterPrecision precision, const char* name); MaterialBuilder& subpass(SubpassType subpassType, - SamplerFormat format, const char* name) noexcept; + SamplerFormat format, const char* name); MaterialBuilder& subpass(SubpassType subpassType, - ParameterPrecision precision, const char* name) noexcept; - MaterialBuilder& subpass(SubpassType subpassType, const char* name) noexcept; + ParameterPrecision precision, const char* name); + MaterialBuilder& subpass(SubpassType subpassType, const char* name); struct Parameter { Parameter() noexcept: parameterType(INVALID) {} // Sampler - Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p, bool ms) - : name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER), multisample(ms) { } + Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p, + bool filterable, bool ms, const char* tn, std::optional s) + : name(paramName), + size(1), + precision(p), + samplerType(t), + format(f), + filterable(filterable), + multisample(ms), + transformName(tn), + stages(s), + parameterType(SAMPLER) {} // Uniform Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p) - : name(paramName), size(typeSize), uniformType(t), precision(p), parameterType(UNIFORM) { } + : name(paramName), + size(typeSize), + uniformType(t), + precision(p), + format{ 0 }, + filterable(false), + multisample(false), + parameterType(UNIFORM) {} // Subpass Parameter(const char* paramName, SubpassType t, SamplerFormat f, ParameterPrecision p) - : name(paramName), size(1), precision(p), subpassType(t), format(f), parameterType(SUBPASS) { } + : name(paramName), + size(1), + precision(p), + subpassType(t), + format(f), + filterable(false), + multisample(false), + parameterType(SUBPASS) {} utils::CString name; size_t size; @@ -655,7 +736,10 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { SamplerType samplerType; SubpassType subpassType; SamplerFormat format; + bool filterable; bool multisample; + utils::CString transformName; + std::optional stages; enum { INVALID, UNIFORM, @@ -686,11 +770,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { struct Constant { utils::CString name; ConstantType type; - union { - int32_t i; - float f; - bool b; - } defaultValue; + ConstantValue defaultValue; }; struct PushConstant { @@ -699,18 +779,24 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { ShaderStage stage; }; + struct CustomVariable { + utils::CString name; + Precision precision = Precision::DEFAULT; + bool hasPrecision = false; + }; + static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT; using Property = filament::Property; using PropertyList = bool[MATERIAL_PROPERTIES_COUNT]; - using VariableList = utils::CString[MATERIAL_VARIABLES_COUNT]; + using VariableList = CustomVariable[MATERIAL_VARIABLES_COUNT]; using OutputList = std::vector; static constexpr size_t MAX_COLOR_OUTPUT = filament::backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; static constexpr size_t MAX_DEPTH_OUTPUT = 1; static_assert(MAX_COLOR_OUTPUT == 8, "When updating MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, manually update post_process_inputs.fs" - " and post_process.fs"); + " and post_process_main.fs"); // Preview the first shader generated by the given CodeGenParams. // This is used to run Static Code Analysis before generating a package. @@ -720,17 +806,16 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { // Returns true if any of the parameter samplers matches the specified type. bool hasSamplerType(SamplerType samplerType) const noexcept; - static constexpr size_t MAX_PARAMETERS_COUNT = 48; static constexpr size_t MAX_SUBPASS_COUNT = 1; static constexpr size_t MAX_BUFFERS_COUNT = 4; - using ParameterList = Parameter[MAX_PARAMETERS_COUNT]; + using ParameterList = std::vector; using SubpassList = Parameter[MAX_SUBPASS_COUNT]; using BufferList = std::vector>; using ConstantList = std::vector; using PushConstantList = std::vector; // returns the number of parameters declared in this material - uint8_t getParameterCount() const noexcept { return mParameterCount; } + size_t getParameterCount() const noexcept { return mParameters.size(); } // returns a list of at least getParameterCount() parameters const ParameterList& getParameters() const noexcept { return mParameters; } @@ -783,7 +868,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { // Multiple calls to findProperties accumulate the property sets across fragment // and vertex shaders in mProperties. bool findProperties(filament::backend::ShaderStage type, - MaterialBuilder::PropertyList& allProperties, + MaterialBuilder::PropertyList const& allProperties, CodeGenParams const& semanticCodeGenParams) noexcept; bool runSemanticAnalysis(MaterialInfo* inOutInfo, @@ -807,21 +892,16 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { bool isLit() const noexcept { return mShading != filament::Shading::UNLIT; } utils::CString mMaterialName; - utils::CString mFileName; + utils::CString mCompilationParameters; class ShaderCode { public: void setLineOffset(size_t offset) noexcept { mLineOffset = offset; } - void setUnresolved(const utils::CString& code) noexcept { - mIncludesResolved = false; + void setCode(const utils::CString& code) noexcept { mCode = code; } - // Resolve all the #include directives, returns true if successful. - bool resolveIncludes(IncludeCallback callback, const utils::CString& fileName) noexcept; - - const utils::CString& getResolved() const noexcept { - assert(mIncludesResolved); + const utils::CString& getCode() const noexcept { return mCode; } @@ -830,13 +910,11 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { private: utils::CString mCode; size_t mLineOffset = 0; - bool mIncludesResolved = false; }; ShaderCode mMaterialFragmentCode; ShaderCode mMaterialVertexCode; - - IncludeCallback mIncludeCallback = nullptr; + std::string_view mMaterialSource; PropertyList mProperties; ParameterList mParameters; @@ -875,7 +953,6 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { bool mShadowMultiplier = false; bool mTransparentShadow = false; - uint8_t mParameterCount = 0; uint8_t mSubpassCount = 0; bool mDoubleSided = false; @@ -892,6 +969,8 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { bool mClearCoatIorChange = true; bool mFlipUV = true; + bool mLinearFog = false; + bool mShadowFarAttenuation = true; bool mMultiBounceAO = false; bool mMultiBounceAOSet = false; @@ -912,11 +991,24 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { filament::UserVariantFilterMask mVariantFilter = {}; bool mNoSamplerValidation = false; + + bool mUseDefaultDepthVariant = false; + + // Default api level is always 1. + uint32_t mApiLevel = 1; }; } // namespace filamat -template<> struct utils::EnableBitMaskOperators - : public std::true_type {}; +template<> +struct utils::EnableBitMaskOperators + : public std::true_type { +}; + +template<> +struct utils::EnableBitMaskOperators + : public std::true_type { +}; + #endif diff --git a/package/android/libs/filament/include/filamat/Package.h b/package/android/libs/filament/include/filamat/Package.h index 93e74a58..52967bfb 100644 --- a/package/android/libs/filament/include/filamat/Package.h +++ b/package/android/libs/filament/include/filamat/Package.h @@ -20,6 +20,7 @@ #include #include #include +#include // memcpy #include #include diff --git a/package/android/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h b/package/android/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h new file mode 100644 index 00000000..ab506c9f --- /dev/null +++ b/package/android/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +namespace filament { + +namespace backend { +class PixelBufferDescriptor; +} + +class Engine; +class Texture; + +struct UTILS_PUBLIC FaceOffsets { + using size_type = size_t; + union { + struct { + size_type px; //!< +x face offset in bytes + size_type nx; //!< -x face offset in bytes + size_type py; //!< +y face offset in bytes + size_type ny; //!< -y face offset in bytes + size_type pz; //!< +z face offset in bytes + size_type nz; //!< -z face offset in bytes + }; + size_type offsets[6]; + }; + size_type operator[](size_t const n) const noexcept { return offsets[n]; } + size_type& operator[](size_t const n) { return offsets[n]; } + FaceOffsets() noexcept = default; + explicit FaceOffsets(size_type const faceSize) noexcept { + px = faceSize * 0; + nx = faceSize * 1; + py = faceSize * 2; + ny = faceSize * 3; + pz = faceSize * 4; + nz = faceSize * 5; + } + FaceOffsets(const FaceOffsets& rhs) noexcept { + px = rhs.px; + nx = rhs.nx; + py = rhs.py; + ny = rhs.ny; + pz = rhs.pz; + nz = rhs.nz; + } + FaceOffsets& operator=(const FaceOffsets& rhs) noexcept { + px = rhs.px; + nx = rhs.nx; + py = rhs.py; + ny = rhs.ny; + pz = rhs.pz; + nz = rhs.nz; + return *this; + } +}; + +/** + * Options for environment prefiltering into reflection map + * + * @see generatePrefilterMipmap() + */ +struct UTILS_PUBLIC PrefilterOptions { + uint16_t sampleCount = 8; //!< sample count used for filtering + bool mirror = true; //!< whether the environment must be mirrored +private: + UTILS_UNUSED uintptr_t reserved[3] = {}; +}; + +UTILS_PUBLIC +void generatePrefilterMipmap(Texture* UTILS_NONNULL texture, Engine& engine, + backend::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, + PrefilterOptions const* UTILS_NULLABLE options = nullptr); + + +} // namespace filament diff --git a/package/android/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h b/package/android/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h index 903c4258..e894e148 100644 --- a/package/android/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h +++ b/package/android/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h @@ -227,7 +227,6 @@ class UTILS_PUBLIC IBLPrefilterContext { filament::Texture* outIrradianceTexture = nullptr); private: - filament::Texture* createIrradianceTexture(); IBLPrefilterContext& mContext; filament::Material* mKernelMaterial = nullptr; filament::Texture* mKernelTexture = nullptr; @@ -325,7 +324,6 @@ class UTILS_PUBLIC IBLPrefilterContext { // TODO: add a callback for when the processing is done? private: - filament::Texture* createReflectionsTexture(); IBLPrefilterContext& mContext; filament::Material* mKernelMaterial = nullptr; filament::Texture* mKernelTexture = nullptr; diff --git a/package/android/libs/filament/include/filament/Box.h b/package/android/libs/filament/include/filament/Box.h index da6638da..35042030 100644 --- a/package/android/libs/filament/include/filament/Box.h +++ b/package/android/libs/filament/include/filament/Box.h @@ -120,8 +120,11 @@ class UTILS_PUBLIC Box { } /** - * @deprecated Use transform() instead - * @see transform() + * Transform a Box by a linear transform and a translation. + * + * @param m a linear transform matrix + * @param box the box to transform + * @return the bounding box of the transformed box */ friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept { return transform(m.upperLeft(), m[3].xyz, box); @@ -183,7 +186,7 @@ struct UTILS_PUBLIC Aabb { * Returns the 8 corner vertices of the AABB. */ Corners getCorners() const { - return Aabb::Corners{ .vertices = { + return Corners{ .vertices = { { min.x, min.y, min.z }, { max.x, min.y, min.z }, { min.x, max.y, min.z }, @@ -235,8 +238,10 @@ struct UTILS_PUBLIC Aabb { } /** - * @deprecated Use transform() instead - * @see transform() + * Applies an affine transformation to the AABB. + * + * @param m the affine transformation to apply + * @return the bounding box of the transformed box */ Aabb transform(const math::mat4f& m) const noexcept { return transform(m.upperLeft(), m[3].xyz, *this); diff --git a/package/android/libs/filament/include/filament/BufferObject.h b/package/android/libs/filament/include/filament/BufferObject.h index 74a4b1ff..bf3ffe3d 100644 --- a/package/android/libs/filament/include/filament/BufferObject.h +++ b/package/android/libs/filament/include/filament/BufferObject.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -54,7 +55,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { using BufferDescriptor = backend::BufferDescriptor; using BindingType = backend::BufferObjectBinding; - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -73,11 +74,38 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { /** * The binding type for this buffer object. (defaults to VERTEX) - * @param BindingType Distinguishes between SSBO, VBO, etc. For now this must be VERTEX. + * @param bindingType Distinguishes between SSBO, VBO, etc. For now this must be VERTEX. * @return A reference to this Builder for chaining calls. */ Builder& bindingType(BindingType bindingType) noexcept; + /** + * Associate an optional name with this BufferObject for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this BufferObject + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this BufferObject for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this BufferObject + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the BufferObject and returns a pointer to it. After creation, the buffer * object is uninitialized. Use BufferObject::setBuffer() to initialize it. @@ -102,7 +130,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { * * @param engine Reference to the filament::Engine associated with this BufferObject. * @param buffer A BufferDescriptor representing the data used to initialize the BufferObject. - * @param byteOffset Offset in bytes into the BufferObject + * @param byteOffset Offset in bytes into the BufferObject. Must be multiple of 4. */ void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0); diff --git a/package/android/libs/filament/include/filament/Camera.h b/package/android/libs/filament/include/filament/Camera.h index 74f34af0..abf8b2c1 100644 --- a/package/android/libs/filament/include/filament/Camera.h +++ b/package/android/libs/filament/include/filament/Camera.h @@ -61,7 +61,7 @@ namespace filament { * filament::Camera* myCamera = engine->createCamera(myCameraEntity); * myCamera->setProjection(45, 16.0/9.0, 0.1, 1.0); * myCamera->lookAt({0, 1.60, 1}, {0, 0, 0}); - * engine->destroyCameraComponent(myCamera); + * engine->destroyCameraComponent(myCameraEntity); * ~~~~~~~~~~~ * * @@ -243,10 +243,10 @@ class UTILS_PUBLIC Camera : public FilamentAPI { /** Utility to set the projection matrix from the field-of-view. * * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. - * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. - * @param direction direction of the \p fovInDegrees parameter. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param direction direction of the \p fovInDegrees parameter. * * @see Fov. */ @@ -256,9 +256,9 @@ class UTILS_PUBLIC Camera : public FilamentAPI { /** Utility to set the projection matrix from the focal length. * * @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0. - * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. */ void setLensProjection(double focalLengthInMillimeters, double aspect, double near, double far); @@ -270,8 +270,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * that is all 3 axis are mapped to [-1, 1]. * * @param projection custom projection matrix used for rendering and culling - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param near distance in world units from the camera to the near plane. + * @param far distance in world units from the camera to the far plane. \p far != \p near. */ void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; @@ -282,8 +282,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * * @param projection custom projection matrix used for rendering * @param projectionForCulling custom projection matrix used for culling - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param near distance in world units from the camera to the near plane. + * @param far distance in world units from the camera to the far plane. \p far != \p near. */ void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, double near, double far) noexcept; @@ -400,14 +400,14 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * engine.getTransformManager().getInstance(camera->getEntity()), model); * ~~~~~~~~~~~ * - * @param model The camera position and orientation provided as a rigid transform matrix. + * @param modelMatrix The camera position and orientation provided as a rigid transform matrix. * * @note The Camera "looks" towards its -z axis * * @warning \p model must be a rigid transform */ - void setModelMatrix(const math::mat4& model) noexcept; - void setModelMatrix(const math::mat4f& model) noexcept; //!< @overload + void setModelMatrix(const math::mat4& modelMatrix) noexcept; + void setModelMatrix(const math::mat4f& modelMatrix) noexcept; //!< @overload /** Set the position of an eye relative to this Camera (head). * diff --git a/package/android/libs/filament/include/filament/Color.h b/package/android/libs/filament/include/filament/Color.h index 30b77856..37939820 100644 --- a/package/android/libs/filament/include/filament/Color.h +++ b/package/android/libs/filament/include/filament/Color.h @@ -194,7 +194,7 @@ inline sRGBColorA Color::toSRGB(LinearColorA const& color) { } inline LinearColor Color::toLinear(RgbType type, math::float3 color) { - return (type == RgbType::LINEAR) ? color : Color::toLinear(color); + return (type == RgbType::LINEAR) ? color : toLinear(color); } // converts an RGBA color to linear space @@ -202,11 +202,11 @@ inline LinearColor Color::toLinear(RgbType type, math::float3 color) { inline LinearColorA Color::toLinear(RgbaType type, math::float4 color) { switch (type) { case RgbaType::sRGB: - return Color::toLinear(color) * math::float4{color.a, color.a, color.a, 1.0f}; + return toLinear(color) * math::float4{color.a, color.a, color.a, 1.0f}; case RgbaType::LINEAR: return color * math::float4{color.a, color.a, color.a, 1.0f}; case RgbaType::PREMULTIPLIED_sRGB: - return Color::toLinear(color); + return toLinear(color); case RgbaType::PREMULTIPLIED_LINEAR: return color; } diff --git a/package/android/libs/filament/include/filament/ColorGrading.h b/package/android/libs/filament/include/filament/ColorGrading.h index e5c8f3ca..3e4916fb 100644 --- a/package/android/libs/filament/include/filament/ColorGrading.h +++ b/package/android/libs/filament/include/filament/ColorGrading.h @@ -64,9 +64,8 @@ class ColorSpace; * Performance * =========== * - * Creating a new ColorGrading object may be more expensive than other Filament objects as a - * 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on - * the CPU. + * Creating a new ColorGrading object may be more expensive than other Filament objects as a LUT may + * need to be generated. The generation of this LUT, if necessary, may happen on the CPU. * * Ordering * ======== @@ -155,6 +154,9 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * 3D texture. For instance, a low quality level will use a 16x16x16 10 bit LUT, a medium * quality level will use a 32x32x32 10 bit LUT, a high quality will use a 32x32x32 16 bit * LUT, and a ultra quality will use a 64x64x64 16 bit LUT. + * + * This setting has no effect if generating a 1D LUT. + * * This overrides the values set by format() and dimensions(). * * The default quality is medium. @@ -169,6 +171,8 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * When color grading is implemented using a 3D LUT, this sets the texture format of * of the LUT. This overrides the value set by quality(). * + * This setting has no effect if generating a 1D LUT. + * * The default is INTEGER * * @param format The desired format of the 3D LUT. @@ -181,6 +185,8 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * When color grading is implemented using a 3D LUT, this sets the dimension of the LUT. * This overrides the value set by quality(). * + * This setting has no effect if generating a 1D LUT. + * * The default is 32 * * @param dim The desired dimension of the LUT. Between 16 and 64. diff --git a/package/android/libs/filament/include/filament/Engine.h b/package/android/libs/filament/include/filament/Engine.h index 2aed8273..c9dee81c 100644 --- a/package/android/libs/filament/include/filament/Engine.h +++ b/package/android/libs/filament/include/filament/Engine.h @@ -17,6 +17,7 @@ #ifndef TNT_FILAMENT_ENGINE_H #define TNT_FILAMENT_ENGINE_H + #include #include @@ -24,10 +25,16 @@ #include #include +#include + +#include +#include +#include #include #include + namespace utils { class Entity; class EntityManager; @@ -36,6 +43,10 @@ class JobSystem; namespace filament { +namespace backend { +class Driver; +} // backend + class BufferObject; class Camera; class ColorGrading; @@ -53,6 +64,7 @@ class Scene; class Skybox; class Stream; class SwapChain; +class Sync; class Texture; class VertexBuffer; class View; @@ -179,6 +191,11 @@ class UTILS_PUBLIC Engine { using DriverConfig = backend::Platform::DriverConfig; using FeatureLevel = backend::FeatureLevel; using StereoscopicType = backend::StereoscopicType; + using Driver = backend::Driver; + using GpuContextPriority = backend::Platform::GpuContextPriority; + using AsynchronousMode = backend::AsynchronousMode; + using AsyncCompletionCallback = std::function; + using AsyncCallId = backend::AsyncCallId; /** * Config is used to define the memory footprint used by the engine, such as the @@ -288,19 +305,37 @@ class UTILS_PUBLIC Engine { */ uint32_t jobSystemThreadCount = 0; - /* - * Number of most-recently destroyed textures to track for use-after-free. + /** + * When uploading vertex or index data, the Filament Metal backend copies data + * into a shared staging area before transferring it to the GPU. This setting controls + * the total size of the buffer used to perform these allocations. + * + * Higher values can improve performance when performing many uploads across a small + * number of frames. * - * This will cause the backend to throw an exception when a texture is freed but still bound - * to a SamplerGroup and used in a draw call. 0 disables completely. + * This buffer remains alive throughout the lifetime of the Engine, so this size adds to the + * memory footprint of the app and should be set as conservative as possible. * - * Currently only respected by the Metal backend. + * A value of 0 disables the shared staging buffer entirely; uploads will acquire an + * individual buffer from a pool of shared buffers. + * + * Only respected by the Metal backend. + */ + size_t metalUploadBufferSizeBytes = 512 * 1024; + + /** + * The action to take if a Drawable cannot be acquired. + * + * Each frame rendered requires a CAMetalDrawable texture, which is + * presented on-screen at the completion of each frame. These are + * limited and provided round-robin style by the system. */ - size_t textureUseAfterFreePoolSize = 0; + bool metalDisablePanicOnDrawableFailure = false; /** * Set to `true` to forcibly disable parallel shader compilation in the backend. * Currently only honored by the GL and Metal backends. + * @deprecated use "backend.disable_parallel_shader_compile" feature flag instead */ bool disableParallelShaderCompile = false; @@ -332,12 +367,16 @@ class UTILS_PUBLIC Engine { uint32_t resourceAllocatorCacheSizeMB = 64; /* - * This value determines for how many frames are texture entries kept in the cache. + * This value determines how many frames texture entries are kept for in the cache. This + * is a soft limit, meaning some texture older than this are allowed to stay in the cache. + * Typically only one texture is evicted per frame. + * The default is 1. */ - uint32_t resourceAllocatorCacheMaxAge = 2; + uint32_t resourceAllocatorCacheMaxAge = 1; /* * Disable backend handles use-after-free checks. + * @deprecated use "backend.disable_handle_use_after_free_check" feature flag instead */ bool disableHandleUseAfterFreeCheck = false; @@ -369,9 +408,62 @@ class UTILS_PUBLIC Engine { * it's a GLES2 context. Ignored on other backends. */ bool forceGLES2Context = false; + + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + * @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead + */ + bool assertNativeWindowIsValid = false; + + /** + * GPU context priority level. Controls GPU work scheduling and preemption. + */ + GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT; + + /** + * The initial size in bytes of the shared uniform buffer used for material instance + * batching. + * + * If the buffer runs out of space during a frame, it will be automatically reallocated + * with a larger capacity. Setting an appropriate initial size can help avoid runtime + * reallocations, which can cause a minor performance stutter, at the cost of higher + * initial memory usage. + */ + uint32_t sharedUboInitialSizeInBytes = 256 * 64; + + /** + * Asynchronous mode for the engine. Defines how asynchronous operations are handled. + * Note that selecting a non-NONE mode does not guarantee asynchronous methods are + * supported, as the underlying backend or the feature flag may override this configuration. + * Always validate availability via Engine::isAsynchronousModeEnabled() before + * invoking asynchronous methods. + */ + AsynchronousMode asynchronousMode = AsynchronousMode::NONE; }; + /** + * Feature flags can be enabled or disabled when the Engine is built. Some Feature flags can + * also be toggled at any time. Feature flags should alawys use their default value unless + * the feature enabled by the flag is faulty. Feature flags provide a last resort way to + * disable problematic features. + * Feature flags are intended to have a short life-time and are regularly removed as features + * mature. + */ + struct FeatureFlag { + char const* UTILS_NONNULL name; //!< name of the feature flag + char const* UTILS_NONNULL description; //!< short description + bool const* UTILS_NONNULL value; //!< pointer to the value of the flag + bool constant = true; //!< whether the flag is constant after construction + }; + + /** + * Returns the list of available feature flags + */ + utils::Slice getFeatureFlags() const noexcept; + #if UTILS_HAS_THREADING using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token); #endif @@ -444,6 +536,21 @@ class UTILS_PUBLIC Engine { */ Builder& paused(bool paused) noexcept; + /** + * Set a feature flag value. This is the only way to set constant feature flags. + * @param name feature name + * @param value true to enable, false to disable + * @return A reference to this Builder for chaining calls. + */ + Builder& feature(char const* UTILS_NONNULL name, bool value) noexcept; + + /** + * Enables a list of features. + * @param list list of feature names to enable. + * @return A reference to this Builder for chaining calls. + */ + Builder& features(std::initializer_list list) noexcept; + #if UTILS_HAS_THREADING /** * Creates the filament Engine asynchronously. @@ -477,7 +584,7 @@ class UTILS_PUBLIC Engine { Platform* UTILS_NULLABLE platform = nullptr, void* UTILS_NULLABLE sharedContext = nullptr, const Config* UTILS_NULLABLE config = nullptr) { - return Engine::Builder() + return Builder() .backend(backend) .platform(platform) .sharedContext(sharedContext) @@ -497,7 +604,7 @@ class UTILS_PUBLIC Engine { Platform* UTILS_NULLABLE platform = nullptr, void* UTILS_NULLABLE sharedContext = nullptr, const Config* UTILS_NULLABLE config = nullptr) { - Engine::Builder() + Builder() .backend(backend) .platform(platform) .sharedContext(sharedContext) @@ -522,6 +629,11 @@ class UTILS_PUBLIC Engine { static Engine* UTILS_NULLABLE getEngine(void* UTILS_NONNULL token); #endif + /** + * @return the Driver instance used by this Engine. + * @see OpenGLPlatform + */ + backend::Driver const* UTILS_NONNULL getDriver() const noexcept; /** * Destroy the Engine instance and all associated resources. @@ -643,6 +755,14 @@ class UTILS_PUBLIC Engine { */ bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept; + /** + * Checks if the engine is set up for asynchronous operation. If it returns true, the + * asynchronous versions of the APIs are available for use. + * + * @return true if the engine supports asynchronous operation. + */ + bool isAsynchronousModeEnabled() const noexcept; + /** * Retrieves the configuration settings of this Engine. * @@ -799,9 +919,19 @@ class UTILS_PUBLIC Engine { */ Fence* UTILS_NONNULL createFence() noexcept; + /** + * Creates a Sync. + * @param callback A callback that will be invoked when the handle for + * the created sync is set + * + * @return A pointer to the newly created Sync. + */ + Sync* UTILS_NONNULL createSync() noexcept; + bool destroy(const BufferObject* UTILS_NULLABLE p); //!< Destroys a BufferObject object. bool destroy(const VertexBuffer* UTILS_NULLABLE p); //!< Destroys an VertexBuffer object. bool destroy(const Fence* UTILS_NULLABLE p); //!< Destroys a Fence object. + bool destroy(const Sync* UTILS_NULLABLE p); //!< Destroys a Sync object. bool destroy(const IndexBuffer* UTILS_NULLABLE p); //!< Destroys an IndexBuffer object. bool destroy(const SkinningBuffer* UTILS_NULLABLE p); //!< Destroys a SkinningBuffer object. bool destroy(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Destroys a MorphTargetBuffer object. @@ -835,6 +965,8 @@ class UTILS_PUBLIC Engine { bool isValid(const VertexBuffer* UTILS_NULLABLE p) const; /** Tells whether a Fence object is valid */ bool isValid(const Fence* UTILS_NULLABLE p) const; + /** Tells whether a Sync object is valid */ + bool isValid(const Sync* UTILS_NULLABLE p) const; /** Tells whether an IndexBuffer object is valid */ bool isValid(const IndexBuffer* UTILS_NULLABLE p) const; /** Tells whether a SkinningBuffer object is valid */ @@ -875,6 +1007,70 @@ class UTILS_PUBLIC Engine { /** Tells whether an InstanceBuffer object is valid */ bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const; + /** + * Retrieve the count of each resource tracked by Engine. + * This is intended for debugging. + * @{ + */ + size_t getBufferObjectCount() const noexcept; + size_t getViewCount() const noexcept; + size_t getSceneCount() const noexcept; + size_t getSwapChainCount() const noexcept; + size_t getStreamCount() const noexcept; + size_t getIndexBufferCount() const noexcept; + size_t getSkinningBufferCount() const noexcept; + size_t getMorphTargetBufferCount() const noexcept; + size_t getInstanceBufferCount() const noexcept; + size_t getVertexBufferCount() const noexcept; + size_t getIndirectLightCount() const noexcept; + size_t getMaterialCount() const noexcept; + size_t getTextureCount() const noexcept; + size_t getSkyboxeCount() const noexcept; + size_t getColorGradingCount() const noexcept; + size_t getRenderTargetCount() const noexcept; + /** @} */ + + /** + * This asynchronously executes user-defined commands. The commands are queued sequentially + * alongside other asynchronous operations (see Texture, VertexBuffer, and IndexBuffer) and + * guaranteed to be executed in the exact order they were invoked. + * + * Beware of overusing this method. It shares the execution queue with other asynchronous tasks + * like texture updates, so flooding it can delay those critical engine tasks. The recommended + * practice is to use this method for resource preparation, such as asset loading(images/meshes). + * This facilitates an efficient chaining pattern, where subsequent asynchronous operations + * (e.g., creating textures/vertex buffers) can be initiated directly within the completion + * callback. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param command The custom command to be executed. + * @param handler The handler from which `onComplete` is invoked. If null, it's called from the + * main thread. + * @param onComplete The callback function that runs once the command has finished. + * @param user The custom data that will be passed as an argument to the `onComplete`. + * @return A unique identifier for the asynchronous call. + */ + AsyncCallId runCommandAsync(utils::Invocable&& command, + backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback onComplete, + void* UTILS_NULLABLE user = nullptr); + + /** + * Cancel the pending asynchronous call pointed to by `id`, which is retrieved whenever you + * invoke a non-blocking version of method on an object, such as `Texture::setImageAsync` or + * `BufferObject::setBufferAsync`. + * + * @param id The unique identifier for the asynchronous call to be canceled. + * @return Returns true upon successful cancellation. It returns false if the asynchronous + * operation cannot be canceled because it is currently running, has finished, or has previously + * been canceled. + */ + bool cancelAsyncCall(AsyncCallId id); + /** * 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 @@ -887,6 +1083,25 @@ class UTILS_PUBLIC Engine { */ 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. @@ -903,7 +1118,7 @@ class UTILS_PUBLIC Engine { * * @see setPaused */ - bool isPaused() const noexcept; + bool isPaused() const noexcept(UTILS_HAS_THREADING); /** * Pause or resume rendering thread. @@ -1021,8 +1236,54 @@ class UTILS_PUBLIC Engine { void resetBackendState() noexcept; #endif + /** + * Get the current time. This is a convenience function that simply returns the + * time in nanosecond since epoch of std::chrono::steady_clock. + * A possible implementation is: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * return std::chrono::steady_clock::now().time_since_epoch().count(); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @return current time in nanosecond since epoch of std::chrono::steady_clock. + * @see Renderer::beginFrame() + */ + static uint64_t getSteadyClockTimeNano() noexcept; + + DebugRegistry& getDebugRegistry() noexcept; + /** + * Check if a feature flag exists + * @param name name of the feature flag to check + * @return true if the feature flag exists, false otherwise + */ + inline bool hasFeatureFlag(char const* UTILS_NONNULL name) noexcept { + return getFeatureFlag(name).has_value(); + } + + /** + * Set the value of a non-constant feature flag. + * @param name name of the feature flag to set + * @param value value to set + * @return true if the value was set, false if the feature flag is constant or doesn't exist. + */ + bool setFeatureFlag(char const* UTILS_NONNULL name, bool value) noexcept; + + /** + * Retrieves the value of any feature flag. + * @param name name of the feature flag + * @return the value of the flag if it exists + */ + std::optional getFeatureFlag(char const* UTILS_NONNULL name) const noexcept; + + /** + * Returns a pointer to a non-constant feature flag value. + * @param name name of the feature flag + * @return a pointer to the feature flag value, or nullptr if the feature flag is constant or doesn't exist + */ + bool* UTILS_NULLABLE getFeatureFlagPtr(char const* UTILS_NONNULL name) const noexcept; + protected: //! \privatesection Engine() noexcept = default; diff --git a/package/android/libs/filament/include/filament/FilamentAPI.h b/package/android/libs/filament/include/filament/FilamentAPI.h index 19d6ba24..d59e051d 100644 --- a/package/android/libs/filament/include/filament/FilamentAPI.h +++ b/package/android/libs/filament/include/filament/FilamentAPI.h @@ -19,6 +19,8 @@ #include #include +#include +#include #include @@ -54,6 +56,37 @@ class UTILS_PUBLIC FilamentAPI { template using BuilderBase = utils::PrivateImplementation; +// This needs to be public because it is used in the following template. +UTILS_PUBLIC void builderMakeName(utils::ImmutableCString& outName, const char* name, size_t len) noexcept; + +template +class UTILS_PUBLIC BuilderNameMixin { +public: + UTILS_DEPRECATED + Builder& name(const char* name, size_t len) noexcept { + builderMakeName(mName, name, len); + return static_cast(*this); + } + + Builder& name(utils::StaticString const& name) noexcept { + builderMakeName(mName, name.data(), name.size()); + return static_cast(*this); + } + + utils::ImmutableCString const& getName() const noexcept { return mName; } + + utils::ImmutableCString const& getNameOrDefault() const noexcept { + if (const auto& name = getName(); !name.empty()) { + return name; + } + static const utils::ImmutableCString sDefaultName = "(none)"; + return sDefaultName; + } + +private: + utils::ImmutableCString mName; +}; + } // namespace filament #endif // TNT_FILAMENT_FILAMENTAPI_H diff --git a/package/android/libs/filament/include/filament/IndexBuffer.h b/package/android/libs/filament/include/filament/IndexBuffer.h index 35b8a10e..9ac7dd3f 100644 --- a/package/android/libs/filament/include/filament/IndexBuffer.h +++ b/package/android/libs/filament/include/filament/IndexBuffer.h @@ -26,7 +26,9 @@ #include #include +#include +#include #include #include @@ -50,6 +52,9 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { public: using BufferDescriptor = backend::BufferDescriptor; + using AsyncCompletionCallback = + std::function; + using AsyncCallId = backend::AsyncCallId; /** * Type of the index buffer @@ -59,7 +64,7 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { UINT = uint8_t(backend::ElementType::UINT), //!< 32-bit indices }; - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -83,6 +88,56 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { */ Builder& bufferType(IndexType indexType) noexcept; + /** + * Associate an optional name with this IndexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this IndexBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this IndexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this IndexBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + + /** + * Specifies a callback that will execute once the resource's data has been fully allocated + * within the GPU memory. This enables the resource creation process to be handled + * asynchronously. + * + * Any asynchronous calls made during a resource's asynchronous creation (using this method) + * are safe because they are queued and executed in sequence. However, invoking regular + * methods on the same resource before it's fully ready is unsafe and may cause undefined + * behavior. Users can call the `isCreationComplete()` method for the resource to confirm + * when the resource is ready for regular API calls. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * @return This Builder, for chaining calls. + */ + Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback = nullptr, + void* UTILS_NULLABLE user = nullptr) noexcept; + /** * Creates the IndexBuffer object and returns a pointer to it. After creation, the index * buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer. @@ -103,22 +158,59 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { }; /** - * Asynchronously copy-initializes a region of this IndexBuffer from the data provided. + * Copy-initializes a region of this IndexBuffer from the data provided. * * @param engine Reference to the filament::Engine to associate this IndexBuffer with. * @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer. * BufferDescriptor points to raw, untyped data that will be interpreted as * either 16-bit or 32-bits indices based on the Type of this IndexBuffer. - * @param byteOffset Offset in *bytes* into the IndexBuffer + * @param byteOffset Offset in *bytes* into the IndexBuffer. Must be multiple of 4. */ void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + /** + * An asynchronous version of `setBuffer()`. + * Copy-initializes a region of this IndexBuffer from the data provided. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param engine Reference to the filament::Engine to associate this IndexBuffer with. + * @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer. + * BufferDescriptor points to raw, untyped data that will be interpreted as + * either 16-bit or 32-bits indices based on the Type of this IndexBuffer. + * @param byteOffset Offset in *bytes* into the IndexBuffer. Must be multiple of 4. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + */ + AsyncCallId setBufferAsync(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset, + backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr); + /** * Returns the size of this IndexBuffer in elements. * @return The number of indices the IndexBuffer holds. */ size_t getIndexCount() const noexcept; + /** + * This non-blocking method checks if the resource has finished creation. If the resource + * creation was initiated asynchronously, it will return true only after all related + * asynchronous tasks are complete. If the resource was created normally without using async + * method, it will always return true. + * + * @return Whether the resource is created. + * + * @see Builder::async() + */ + bool isCreationComplete() const noexcept; + protected: // prevent heap allocation ~IndexBuffer() = default; diff --git a/package/android/libs/filament/include/filament/IndirectLight.h b/package/android/libs/filament/include/filament/IndirectLight.h index c230dac8..f5c2cfbe 100644 --- a/package/android/libs/filament/include/filament/IndirectLight.h +++ b/package/android/libs/filament/include/filament/IndirectLight.h @@ -158,6 +158,8 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * * @return This Builder, for chaining calls. * + * @see Material::Builder::sphericalHarmonicsBandCount() + * * @note * Because the coefficients are pre-scaled, `sh[0]` is the environment's * average irradiance. @@ -337,13 +339,24 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { static math::float4 getColorEstimate(const math::float3 sh[UTILS_NONNULL 9], math::float3 direction) noexcept; - - /** @deprecated use static versions instead */ - UTILS_DEPRECATED + /** + * Helper to estimate the direction of the dominant light in the environment represented by + * spherical harmonics. + * Spherical harmonics must be set in the Builder or the result is undefined. + * @see getDirectionEstimate(const math::float3) + * @see Builder::irradiance(uint8_t, math::float3 const*) + * @see Builder::radiance(uint8_t, math::float3 const*) + */ math::float3 getDirectionEstimate() const noexcept; - /** @deprecated use static versions instead */ - UTILS_DEPRECATED + /** + * Helper to estimate the color and relative intensity of the environment represented by + * spherical harmonics in a given direction. + * Spherical harmonics must be set in the Builder or the result is undefined. + * @see getColorEstimate(const math::float3, math::float3) + * @see Builder::irradiance(uint8_t, math::float3 const*) + * @see Builder::radiance(uint8_t, math::float3 const*) + */ math::float4 getColorEstimate(math::float3 direction) const noexcept; protected: diff --git a/package/android/libs/filament/include/filament/InstanceBuffer.h b/package/android/libs/filament/include/filament/InstanceBuffer.h index 2135152d..8e7f788c 100644 --- a/package/android/libs/filament/include/filament/InstanceBuffer.h +++ b/package/android/libs/filament/include/filament/InstanceBuffer.h @@ -21,6 +21,7 @@ #include #include +#include #include @@ -38,13 +39,13 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { struct BuilderDetails; public: - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: /** - * @param instanceCount the number of instances this InstanceBuffer will support, must be + * @param instanceCount The number of instances this InstanceBuffer will support, must be * >= 1 and <= \c Engine::getMaxAutomaticInstances() * @see Engine::getMaxAutomaticInstances */ @@ -70,10 +71,37 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { */ Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept; + /** + * Associate an optional name with this InstanceBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this InstanceBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this InstanceBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this InstanceBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the InstanceBuffer object and returns a pointer to it. */ - InstanceBuffer* UTILS_NONNULL build(Engine& engine); + InstanceBuffer* UTILS_NONNULL build(Engine& engine) const; private: friend class FInstanceBuffer; @@ -96,6 +124,13 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { void setLocalTransforms(math::mat4f const* UTILS_NONNULL localTransforms, size_t count, size_t offset = 0); + /** + * Returns the local transform for a given instance. + * @param index The index of the instance. + * @return The local transform of the instance. + */ + math::mat4f const& getLocalTransform(size_t index); + protected: // prevent heap allocation ~InstanceBuffer() = default; diff --git a/package/android/libs/filament/include/filament/Material.h b/package/android/libs/filament/include/filament/Material.h index b4b9bbed..3625bf6e 100644 --- a/package/android/libs/filament/include/filament/Material.h +++ b/package/android/libs/filament/include/filament/Material.h @@ -69,6 +69,20 @@ class UTILS_PUBLIC Material : public FilamentAPI { using ShaderModel = backend::ShaderModel; using SubpassType = backend::SubpassType; + /** + * Defines whether a material instance should use UBO batching or not. + */ + enum class UboBatchingMode { + /** + * For default, it follows the engine settings. + * If UBO batching is enabled on the engine and the material domain is SURFACE, it + * turns on the UBO batching. Otherwise, it turns off the UBO batching. + */ + DEFAULT, + //! Disable the Ubo Batching for this material + DISABLED, + }; + /** * Holds information about a material parameter. */ @@ -103,6 +117,11 @@ class UTILS_PUBLIC Material : public FilamentAPI { Builder& operator=(Builder const& rhs) noexcept; Builder& operator=(Builder&& rhs) noexcept; + enum class ShadowSamplingQuality : uint8_t { + HARD, // 2x2 PCF + LOW // 3x3 gaussian filter + }; + /** * Specifies the material data. The material data is a binary blob produced by * libfilamat or by matc. @@ -113,10 +132,10 @@ class UTILS_PUBLIC Material : public FilamentAPI { Builder& package(const void* UTILS_NONNULL payload, size_t size); template - using is_supported_constant_parameter_t = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value>::type; + using is_supported_constant_parameter_t = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v>; /** * Specialize a constant parameter specified in the material definition with a concrete @@ -140,6 +159,33 @@ class UTILS_PUBLIC Material : public FilamentAPI { return constant(name, strlen(name), value); } + /** + * Sets the quality of the indirect lights computations. This is only taken into account + * if this material is lit and in the surface domain. This setting will affect the + * IndirectLight computation if one is specified on the Scene and Spherical Harmonics + * are used for the irradiance. + * + * @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default). + * @return Reference to this Builder for chaining calls. + * @see IndirectLight + */ + Builder& sphericalHarmonicsBandCount(size_t shBandCount) noexcept; + + /** + * Set the quality of shadow sampling. This is only taken into account + * if this material is lit and in the surface domain. + * @param quality + * @return + */ + Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept; + + /** + * Set the batching mode of the instances created from this material. + * @param uboBatchingMode + * @return + */ + Builder& uboBatching(UboBatchingMode uboBatchingMode) noexcept; + /** * Creates the Material object and returns a pointer to it. * @@ -152,7 +198,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - Material* UTILS_NULLABLE build(Engine& engine); + Material* UTILS_NULLABLE build(Engine& engine) const; private: friend class FMaterial; }; @@ -325,6 +371,24 @@ class UTILS_PUBLIC Material : public FilamentAPI { //! Indicates whether an existing parameter is a sampler or not. bool isSampler(const char* UTILS_NONNULL name) const noexcept; + /** + * Returns a view of the material source (.mat which is a JSON-ish file) string, + * if it has been set. Otherwise, it returns a view of an empty string. + * The lifetime of the string_view is tied to the lifetime of the Material. + */ + std::string_view getSource() const noexcept; + /** + * + * Gets the name of the transform field associated for the given sampler parameter. + * In the case where the parameter does not have a transform name field, it will return nullptr. + * + * @param samplerName the name of the sampler parameter to query. + * + * @return If exists, the transform name value otherwise returns a nullptr. + */ + const char* UTILS_NULLABLE getParameterTransformName( + const char* UTILS_NONNULL samplerName) const noexcept; + /** * Sets the value of the given parameter on this material's default instance. * diff --git a/package/android/libs/filament/include/filament/MaterialChunkType.h b/package/android/libs/filament/include/filament/MaterialChunkType.h index 4a4561c1..e321b386 100644 --- a/package/android/libs/filament/include/filament/MaterialChunkType.h +++ b/package/android/libs/filament/include/filament/MaterialChunkType.h @@ -45,18 +45,20 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialEssl1 = charTo64bitNum("MAT_ESS1"), MaterialSpirv = charTo64bitNum("MAT_SPIR"), MaterialMetal = charTo64bitNum("MAT_METL"), + MaterialWgsl = charTo64bitNum("MAT_WGSL"), MaterialMetalLibrary = charTo64bitNum("MAT_MLIB"), MaterialShaderModels = charTo64bitNum("MAT_SMDL"), - MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"), - MaterialUniformBindings = charTo64bitNum("MAT_UNIF"), MaterialBindingUniformInfo = charTo64bitNum("MAT_UFRM"), MaterialAttributeInfo = charTo64bitNum("MAT_ATTR"), + MaterialDescriptorBindingsInfo = charTo64bitNum("MAT_DBDI"), + MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"), MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialConstants = charTo64bitNum("MAT_CONS"), MaterialPushConstants = charTo64bitNum("MAT_PCON"), MaterialName = charTo64bitNum("MAT_NAME"), MaterialVersion = charTo64bitNum("MAT_VERS"), + MaterialCompilationParameters = charTo64bitNum("MAT_CPRM"), MaterialCacheId = charTo64bitNum("MAT_UUID"), MaterialFeatureLevel = charTo64bitNum("MAT_FEAT"), MaterialShading = charTo64bitNum("MAT_SHAD"), @@ -92,10 +94,15 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialVertexDomain = charTo64bitNum("MAT_VEDO"), MaterialInterpolation = charTo64bitNum("MAT_INTR"), + MaterialStereoscopicType = charTo64bitNum("MAT_STER"), DictionaryText = charTo64bitNum("DIC_TEXT"), DictionarySpirv = charTo64bitNum("DIC_SPIR"), DictionaryMetalLibrary = charTo64bitNum("DIC_MLIB"), + + MaterialCrc32 = charTo64bitNum("MAT_CRC "), + + MaterialSource = charTo64bitNum("MAT_SRC "), }; } // namespace filamat diff --git a/package/android/libs/filament/include/filament/MaterialEnums.h b/package/android/libs/filament/include/filament/MaterialEnums.h index c15c6c45..fb2bea79 100644 --- a/package/android/libs/filament/include/filament/MaterialEnums.h +++ b/package/android/libs/filament/include/filament/MaterialEnums.h @@ -28,7 +28,16 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 53; +static constexpr size_t MATERIAL_VERSION = 68; + +// Those are the api levels that are used in the source material file (.mat) +// +// The level used for the apis that are already public and stable. Any released api supports +// backward compatibility (i.e. No breaking changes will be introduced for those apis.) +static constexpr uint32_t RELEASED_MATERIAL_API_LEVEL = 1; +// The level used for the apis that are currently under development or development has completed +// but may introduce breaking changes. +static constexpr uint32_t UNSTABLE_MATERIAL_API_LEVEL = 2; /** * Supported shading models @@ -203,7 +212,7 @@ enum class ReflectionMode : uint8_t { // can't really use std::underlying_type::type because the driver takes a uint32_t using AttributeBitset = utils::bitset32; -static constexpr size_t MATERIAL_PROPERTIES_COUNT = 27; +static constexpr size_t MATERIAL_PROPERTIES_COUNT = 31; enum class Property : uint8_t { BASE_COLOR, //!< float4, all shading models ROUGHNESS, //!< float, lit shading models only @@ -230,8 +239,12 @@ enum class Property : uint8_t { ABSORPTION, //!< float3, how much light is absorbed by the material TRANSMISSION, //!< float, how much light is refracted through the material IOR, //!< float, material's index of refraction + DISPERSION, //!< float, material's dispersion MICRO_THICKNESS, //!< float, thickness of the thin layer BENT_NORMAL, //!< float3, all shading models only, except unlit + SPECULAR_FACTOR, //!< float, lit shading models only, except subsurface and cloth + SPECULAR_COLOR_FACTOR, //!< float3, lit shading models only, except subsurface and cloth + SHADOW_STRENGTH, //!< float, [0, 1] strength of shadows received by this material // when adding new Properties, make sure to update MATERIAL_PROPERTIES_COUNT }; @@ -255,4 +268,7 @@ enum class UserVariantFilterBit : UserVariantFilterMask { template<> struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableIntegerOperators + : public std::true_type {}; + #endif diff --git a/package/android/libs/filament/include/filament/MaterialInstance.h b/package/android/libs/filament/include/filament/MaterialInstance.h index 2b8aaa9a..b052c8f7 100644 --- a/package/android/libs/filament/include/filament/MaterialInstance.h +++ b/package/android/libs/filament/include/filament/MaterialInstance.h @@ -19,7 +19,7 @@ #include #include - +#include #include #include @@ -56,35 +56,36 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { }; public: - using CullingMode = filament::backend::CullingMode; + using CullingMode = backend::CullingMode; + // ReSharper disable once CppRedundantQualifier using TransparencyMode = filament::TransparencyMode; - using DepthFunc = filament::backend::SamplerCompareFunc; - using StencilCompareFunc = filament::backend::SamplerCompareFunc; - using StencilOperation = filament::backend::StencilOperation; - using StencilFace = filament::backend::StencilFace; + using DepthFunc = backend::SamplerCompareFunc; + using StencilCompareFunc = backend::SamplerCompareFunc; + using StencilOperation = backend::StencilOperation; + using StencilFace = backend::StencilFace; template - using is_supported_parameter_t = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || + using is_supported_parameter_t = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || // these types are slower as they need a layout conversion - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value - >::type; + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v + >; /** * Creates a new MaterialInstance using another MaterialInstance as a template for initialization. @@ -121,13 +122,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated string literal */ template> - inline void setParameter(StringLiteral name, T const& value) { + void setParameter(StringLiteral const name, T const& value) { setParameter(name.data, name.size, value); } /** inline helper to provide the name as a null-terminated C string */ template> - inline void setParameter(const char* UTILS_NONNULL name, T const& value) { + void setParameter(const char* UTILS_NONNULL name, T const& value) { setParameter(name, strlen(name), value); } @@ -148,14 +149,14 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated string literal */ template> - inline void setParameter(StringLiteral name, const T* UTILS_NONNULL values, size_t count) { + void setParameter(StringLiteral const name, const T* UTILS_NONNULL values, size_t const count) { setParameter(name.data, name.size, values, count); } /** inline helper to provide the name as a null-terminated C string */ template> - inline void setParameter(const char* UTILS_NONNULL name, - const T* UTILS_NONNULL values, size_t count) { + void setParameter(const char* UTILS_NONNULL name, + const T* UTILS_NONNULL values, size_t const count) { setParameter(name, strlen(name), values, count); } @@ -176,14 +177,14 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler); /** inline helper to provide the name as a null-terminated string literal */ - inline void setParameter(StringLiteral name, - Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { + void setParameter(StringLiteral const name, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { setParameter(name.data, name.size, texture, sampler); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* UTILS_NONNULL name, - Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { + void setParameter(const char* UTILS_NONNULL name, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { setParameter(name, strlen(name), texture, sampler); } @@ -202,12 +203,12 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { RgbType type, math::float3 color); /** inline helper to provide the name as a null-terminated string literal */ - inline void setParameter(StringLiteral name, RgbType type, math::float3 color) { + void setParameter(StringLiteral const name, RgbType const type, math::float3 const color) { setParameter(name.data, name.size, type, color); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* UTILS_NONNULL name, RgbType type, math::float3 color) { + void setParameter(const char* UTILS_NONNULL name, RgbType const type, math::float3 const color) { setParameter(name, strlen(name), type, color); } @@ -226,12 +227,12 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { RgbaType type, math::float4 color); /** inline helper to provide the name as a null-terminated string literal */ - inline void setParameter(StringLiteral name, RgbaType type, math::float4 color) { + void setParameter(StringLiteral const name, RgbaType const type, math::float4 const color) { setParameter(name.data, name.size, type, color); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* UTILS_NONNULL name, RgbaType type, math::float4 color) { + void setParameter(const char* UTILS_NONNULL name, RgbaType const type, math::float4 const color) { setParameter(name, strlen(name), type, color); } @@ -251,13 +252,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated C string */ template> - inline T getParameter(StringLiteral name) const { + T getParameter(StringLiteral const name) const { return getParameter(name.data, name.size); } /** inline helper to provide the name as a null-terminated C string */ template> - inline T getParameter(const char* UTILS_NONNULL name) const { + T getParameter(const char* UTILS_NONNULL name) const { return getParameter(name, strlen(name)); } @@ -376,11 +377,22 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { */ void setCullingMode(CullingMode culling) noexcept; + /** + * Overrides the default triangle culling state that was set on the material separately for the + * color and shadow passes + */ + void setCullingMode(CullingMode colorPassCullingMode, CullingMode shadowPassCullingMode) noexcept; + /** * Returns the face culling mode. */ CullingMode getCullingMode() const noexcept; + /** + * Returns the face culling mode for the shadow passes. + */ + CullingMode getShadowCullingMode() const noexcept; + /** * Overrides the default color-buffer write state that was set on the material. */ @@ -518,6 +530,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { void setStencilWriteMask(uint8_t writeMask, StencilFace face = StencilFace::FRONT_AND_BACK) noexcept; + /** + * PostProcess and compute domain material instance must be commited manually. This call has + * no effect on surface domain materials. + * @param engine Filament engine + */ + void commit(Engine& engine) const; + protected: // prevent heap allocation ~MaterialInstance() = default; diff --git a/package/android/libs/filament/include/filament/MorphTargetBuffer.h b/package/android/libs/filament/include/filament/MorphTargetBuffer.h index 655bb8d8..594f1a6c 100644 --- a/package/android/libs/filament/include/filament/MorphTargetBuffer.h +++ b/package/android/libs/filament/include/filament/MorphTargetBuffer.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -39,7 +40,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { struct BuilderDetails; public: - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -63,6 +64,33 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { */ Builder& count(size_t count) noexcept; + /** + * Associate an optional name with this MorphTargetBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this MorphTargetBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this MorphTargetBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this MorphTargetBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the MorphTargetBuffer object and returns a pointer to it. * diff --git a/package/android/libs/filament/include/filament/Options.h b/package/android/libs/filament/include/filament/Options.h index 3966053e..422a0222 100644 --- a/package/android/libs/filament/include/filament/Options.h +++ b/package/android/libs/filament/include/filament/Options.h @@ -70,8 +70,8 @@ enum class BlendMode : uint8_t { * * \note * Dynamic resolution is only supported on platforms where the time to render - * a frame can be measured accurately. Dynamic resolution is currently only - * supported on Android. + * a frame can be measured accurately. On platforms where this is not supported, + * Dynamic Resolution can't be enabled unless minScale == maxScale. * * @see Renderer::FrameRateOptions * @@ -86,12 +86,15 @@ 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. + * + * caveat: currently, 'quality' is always set to LOW if the View is TRANSLUCENT. */ QualityLevel quality = QualityLevel::LOW; }; @@ -164,7 +167,9 @@ struct BloomOptions { }; /** - * Options to control large-scale fog in the scene + * Options to control large-scale fog in the scene. Materials can enable the `linearFog` property, + * which uses a simplified, linear equation for fog calculation; in this mode, the heightFalloff + * is ignored as well as the mipmap selection in IBL or skyColor mode. */ struct FogOptions { /** @@ -183,7 +188,7 @@ struct FogOptions { float cutOffDistance = INFINITY; /** - * fog's maximum opacity between 0 and 1 + * fog's maximum opacity between 0 and 1. Ignored in `linearFog` mode. */ float maximumOpacity = 1.0f; @@ -193,12 +198,15 @@ struct FogOptions { float height = 0.0f; /** - * How fast the fog dissipates with altitude. heightFalloff has a unit of [1/m]. + * How fast the fog dissipates with the altitude. heightFalloff has a unit of [1/m]. * It can be expressed as 1/H, where H is the altitude change in world units [m] that causes a * factor 2.78 (e) change in fog density. * * A falloff of 0 means the fog density is constant everywhere and may result is slightly * faster computations. + * + * In `linearFog` mode, only use to compute the slope of the linear equation. Completely + * ignored if set to 0. */ float heightFalloff = 1.0f; @@ -220,7 +228,7 @@ struct FogOptions { LinearColor color = { 1.0f, 1.0f, 1.0f }; /** - * Extinction factor in [1/m] at altitude 'height'. The extinction factor controls how much + * Extinction factor in [1/m] at an altitude 'height'. The extinction factor controls how much * light is absorbed and out-scattered per unit of distance. Each unit of extinction reduces * the incoming light to 37% of its original value. * @@ -229,11 +237,16 @@ struct FogOptions { * the composition of the fog/atmosphere. * * For historical reason this parameter is called `density`. + * + * In `linearFog` mode this is the slope of the linear equation if heightFalloff is set to 0. + * Otherwise, heightFalloff affects the slope calculation such that it matches the slope of + * the standard equation at the camera height. */ float density = 0.1f; /** * Distance in world units [m] from the camera where the Sun in-scattering starts. + * Ignored in `linearFog` mode. */ float inScatteringStart = 0.0f; @@ -242,6 +255,7 @@ struct FogOptions { * is scattered (by the fog) towards the camera. * Size of the Sun in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100). * Smaller values result is a larger scattering size. + * Ignored in `linearFog` mode. */ float inScatteringSize = -1.0f; @@ -269,6 +283,8 @@ struct FogOptions { * * `fogColorFromIbl` is ignored when skyTexture is specified. * + * In `linearFog` mode mipmap level 0 is always used. + * * @see Texture * @see fogColorFromIbl */ @@ -283,7 +299,7 @@ struct FogOptions { /** * Options to control Depth of Field (DoF) effect in the scene. * - * cocScale can be used to set the depth of field blur independently from the camera + * cocScale can be used to set the depth of field blur independently of the camera * aperture, e.g. for artistic reasons. This can be achieved by setting: * cocScale = cameraAperture / desiredDoFAperture * @@ -373,18 +389,30 @@ struct RenderQuality { * @see setAmbientOcclusionOptions() */ struct AmbientOcclusionOptions { + enum class AmbientOcclusionType : uint8_t { + SAO, //!< use Scalable Ambient Occlusion + GTAO, //!< use Ground Truth-Based Ambient Occlusion + }; + + AmbientOcclusionType aoType = AmbientOcclusionType::SAO;//!< Type of ambient occlusion algorithm. float radius = 0.3f; //!< Ambient Occlusion radius in meters, between 0 and ~10. float power = 1.0f; //!< Controls ambient occlusion's contrast. Must be positive. - float bias = 0.0005f; //!< Self-occlusion bias in meters. Use to avoid self-occlusion. Between 0 and a few mm. + + /** + * Self-occlusion bias in meters. Use to avoid self-occlusion. + * Between 0 and a few mm. No effect when aoType set to GTAO + */ + float bias = 0.0005f; + float resolution = 0.5f;//!< How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0. float intensity = 1.0f; //!< Strength of the Ambient Occlusion effect. float bilateralThreshold = 0.05f; //!< depth distance that constitute an edge for filtering - QualityLevel quality = QualityLevel::LOW; //!< affects # of samples used for AO. - QualityLevel lowPassFilter = QualityLevel::MEDIUM; //!< affects AO smoothness + QualityLevel quality = QualityLevel::LOW; //!< affects # of samples used for AO and params for filtering + QualityLevel lowPassFilter = QualityLevel::MEDIUM; //!< affects AO smoothness. Recommend setting to HIGH when aoType set to GTAO. QualityLevel upsampling = QualityLevel::LOW; //!< affects AO buffer upsampling quality bool enabled = false; //!< enables or disables screen-space ambient occlusion bool bentNormals = false; //!< enables bent normals computation from AO, and specular AO - float minHorizonAngleRad = 0.0f; //!< min angle in radian to consider + float minHorizonAngleRad = 0.0f; //!< min angle in radian to consider. No effect when aoType set to GTAO. /** * Screen Space Cone Tracing (SSCT) options * Ambient shadows from dominant light @@ -402,6 +430,29 @@ struct AmbientOcclusionOptions { bool enabled = false; //!< enables or disables SSCT }; Ssct ssct; // %codegen_skip_javascript% %codegen_java_flatten% + + /** + * Ground Truth-base Ambient Occlusion (GTAO) options + */ + struct Gtao { + uint8_t sampleSliceCount = 4; //!< # of slices. Higher value makes less noise. + uint8_t sampleStepsPerSlice = 3; //!< # of steps the radius is divided into for integration. Higher value makes less bias. + float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0. No effect when useVisibilityBitmasks sets to true. + + /** + * Enables or disables visibility bitmasks mode. Notes that bent normal doesn't work under this mode. + * Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation. + */ + bool useVisibilityBitmasks = false; + float constThickness = 0.5f; //!< constant thickness value of objects on the screen in world space. Only take effect when useVisibilityBitmasks is set to true. + + /** + * Increase thickness with distance to maintain detail on distant surfaces. + * Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation. + */ + bool linearThickness = false; + }; + Gtao gtao; // %codegen_skip_javascript% %codegen_java_flatten% }; /** @@ -438,16 +489,15 @@ struct MultiSampleAntiAliasingOptions { * @see setTemporalAntiAliasingOptions() */ struct TemporalAntiAliasingOptions { - float filterWidth = 1.0f; //!< reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother) + float filterWidth = 1.0f; //!< @deprecated has no effect. float feedback = 0.12f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). float lodBias = -1.0f; //!< texturing lod bias (typically -1 or -2) float sharpness = 0.0f; //!< post-TAA sharpen, especially useful when upscaling is true. bool enabled = false; //!< enables or disables temporal anti-aliasing - bool upscaling = false; //!< 4x TAA upscaling. Disables Dynamic Resolution. [BETA] + float upscaling = 1.0f; //!< Upscaling factor. Disables Dynamic Resolution. [BETA] enum class BoxType : uint8_t { AABB, //!< use an AABB neighborhood - VARIANCE, //!< use the variance of the neighborhood (not recommended) AABB_VARIANCE //!< use both AABB and variance }; @@ -468,6 +518,7 @@ struct TemporalAntiAliasingOptions { bool filterHistory = true; //!< whether to filter the history buffer bool filterInput = true; //!< whether to apply the reconstruction filter to the input bool useYCoCg = false; //!< whether to use the YcoCg color-space for history rejection + bool hdr = true; //!< set to true for HDR content BoxType boxType = BoxType::AABB; //!< type of color gamut box BoxClipping boxClipping = BoxClipping::ACCURATE; //!< clipping algorithm JitterPattern jitterPattern = JitterPattern::HALTON_23_X16; //! Jitter Pattern diff --git a/package/android/libs/filament/include/filament/RenderTarget.h b/package/android/libs/filament/include/filament/RenderTarget.h index fc76111d..02eb6714 100644 --- a/package/android/libs/filament/include/filament/RenderTarget.h +++ b/package/android/libs/filament/include/filament/RenderTarget.h @@ -81,7 +81,7 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { }; //! Use Builder to construct a RenderTarget object instance - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -118,7 +118,7 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { Builder& mipLevel(AttachmentPoint attachment, uint8_t level) noexcept; /** - * Sets the cubemap face for a given attachment point. + * Sets the face for cubemap textures at the given attachment point. * * @param attachment The attachment point. * @param face The associated cubemap face. @@ -127,7 +127,12 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { Builder& face(AttachmentPoint attachment, CubemapFace face) noexcept; /** - * Sets the layer for a given attachment point (for 3D textures). + * Sets an index of a single layer for 2d array, cubemap array, and 3d textures at the given + * attachment point. + * + * For cubemap array textures, layer is translated into an array index and face according to + * - index: layer / 6 + * - face: layer % 6 * * @param attachment The attachment point. * @param layer The associated cubemap layer. @@ -135,6 +140,27 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { */ Builder& layer(AttachmentPoint attachment, uint32_t layer) noexcept; + /** + * Sets the starting index of the 2d array textures for multiview at the given attachment + * point. + * + * This requires COLOR and DEPTH attachments (if set) to be of 2D array textures. + * + * @param attachment The attachment point. + * @param layerCount The number of layers used for multiview, starting from baseLayer. + * @param baseLayer The starting index of the 2d array texture. + * @return A reference to this Builder for chaining calls. + */ + Builder& multiview(AttachmentPoint attachment, uint8_t layerCount, uint8_t baseLayer = 0) noexcept; + + /** + * Sets the number of samples used for MSAA (Multisample Anti-Aliasing). + * + * @param samples The number of samples used for multisampling. + * @return A reference to this Builder for chaining calls. + */ + Builder& samples(uint8_t samples) noexcept; + /** * Creates the RenderTarget object and returns a pointer to it. * diff --git a/package/android/libs/filament/include/filament/RenderableManager.h b/package/android/libs/filament/include/filament/RenderableManager.h index 363ef2d7..1428ed1e 100644 --- a/package/android/libs/filament/include/filament/RenderableManager.h +++ b/package/android/libs/filament/include/filament/RenderableManager.h @@ -220,7 +220,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * the renderable are immutable. * STATIC geometry has the same restrictions as STATIC_BOUNDS, but in addition disallows * skinning, morphing and changing the VertexBuffer or IndexBuffer in any way. - * @param enable whether this renderable has static bounds. false by default. + * @param type type of geometry. */ Builder& geometryType(GeometryType type) noexcept; @@ -297,7 +297,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { Builder& priority(uint8_t priority) noexcept; /** - * Set the channel this renderable is associated to. There can be 4 channels. + * Set the channel this renderable is associated to. There can be 8 channels. * All renderables in a given channel are rendered together, regardless of anything else. * They are sorted as usual within a channel. * Channels work similarly to priorities, except that they enforce the strongest ordering. @@ -305,7 +305,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * Channels 0 and 1 may not have render primitives using a material with `refractionType` * set to `screenspace`. * - * @param channel clamped to the range [0..3], defaults to 2. + * @param channel clamped to the range [0..7], defaults to 2. * * @return Builder reference for chaining calls. * @@ -454,7 +454,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * * @param primitiveIndex zero-based index of the primitive, must be less than the primitive * count passed to Builder constructor - * @param indicesAndWeightsVectors pairs of bone index and bone weight for all vertices of + * @param indicesAndWeightsVector pairs of bone index and bone weight for all vertices of * the primitive sequentially * * @return Builder reference for chaining calls. @@ -489,43 +489,20 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * For standard morphing, A MorphTargetBuffer must be provided. * Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets. * - * For legacy morphing, the attached VertexBuffer must provide data in the - * appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only - * supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must - * be enabled on the material definition: either via the legacyMorphing material attribute - * or by calling filamat::MaterialBuilder::useLegacyMorphing(). - * * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis * to advance the animation. */ Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; - /** - * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead - */ - Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, - size_t offset, size_t count) noexcept; - - /** - * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead - */ - inline Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { - return morphing(level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); - } - /** * Specifies the the range of the MorphTargetBuffer to use with this primitive. * * @param level the level of detail (lod), only 0 can be specified * @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor * @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices) - * @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3) */ - Builder& morphing(uint8_t level, size_t primitiveIndex, - size_t offset, size_t count) noexcept; + Builder& morphing(uint8_t level, + size_t primitiveIndex, size_t offset) noexcept; /** @@ -620,21 +597,6 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { friend class FEngine; friend class FRenderPrimitive; friend class FRenderableManager; - struct Entry { - VertexBuffer* UTILS_NULLABLE vertices = nullptr; - IndexBuffer* UTILS_NULLABLE indices = nullptr; - size_t offset = 0; - size_t count = 0; - MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr; - PrimitiveType type = PrimitiveType::TRIANGLES; - uint16_t blendOrder = 0; - bool globalBlendOrderEnabled = false; - struct { - MorphTargetBuffer* UTILS_NULLABLE buffer = nullptr; - size_t offset = 0; - size_t count = 0; - } morphing; - }; }; /** @@ -786,25 +748,13 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /** * Associates a MorphTargetBuffer to the given primitive. */ - void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - size_t offset, size_t count); - - /** @deprecated */ - void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count); - - /** @deprecated */ - inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { - setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); - } + void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex, + size_t offset); /** - * Get a MorphTargetBuffer to the given primitive or null if it doesn't exist. + * Get a MorphTargetBuffer to the given renderable or null if it doesn't exist. */ - MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBufferAt(Instance instance, - uint8_t level, size_t primitiveIndex) const noexcept; + MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBuffer(Instance instance) const noexcept; /** * Gets the number of morphing in the given entity. @@ -833,6 +783,13 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { */ size_t getPrimitiveCount(Instance instance) const noexcept; + /** + * Returns the number of instances for this renderable. + * @param instance Instance of the component obtained from getInstance(). + * @return The number of instances. + */ + size_t getInstanceCount(Instance instance) const noexcept; + /** * Changes the material instance binding for the given primitive. * @@ -848,6 +805,13 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { void setMaterialInstanceAt(Instance instance, size_t primitiveIndex, MaterialInstance const* UTILS_NONNULL materialInstance); + /** + * Clear the MaterialInstance for the given primitive. + * @param instance Renderable's instance + * @param primitiveIndex Primitive index + */ + void clearMaterialInstanceAt(Instance instance, size_t primitiveIndex); + /** * Retrieves the material instance that is bound to the given primitive. */ @@ -895,20 +859,20 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /*! \cond PRIVATE */ template struct is_supported_vector_type { - using type = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value - >::type; + using type = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v + >; }; template struct is_supported_index_type { - using type = typename std::enable_if< - std::is_same::value || - std::is_same::value - >::type; + using type = std::enable_if_t< + std::is_same_v || + std::is_same_v + >; }; /*! \endcond */ diff --git a/package/android/libs/filament/include/filament/Renderer.h b/package/android/libs/filament/include/filament/Renderer.h index fdc291b1..53c13cab 100644 --- a/package/android/libs/filament/include/filament/Renderer.h +++ b/package/android/libs/filament/include/filament/Renderer.h @@ -22,9 +22,11 @@ #include #include +#include #include +#include #include namespace filament { @@ -81,6 +83,50 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { UTILS_DEPRECATED uint64_t vsyncOffsetNanos = 0; }; + /** + * Timing information about a frame + * @see getFrameInfoHistory() + */ + struct FrameInfo { + /** duration in nanosecond since epoch of std::steady_clock */ + using time_point_ns = int64_t; + /** duration in nanosecond on the std::steady_clock */ + using duration_ns = int64_t; + static constexpr time_point_ns INVALID = -1; //!< value not supported + static constexpr time_point_ns PENDING = -2; //!< value not yet available + uint32_t frameId; //!< monotonically increasing frame identifier + duration_ns gpuFrameDuration; //!< frame duration on the GPU in nanosecond [ns] + duration_ns denoisedGpuFrameDuration; //!< denoised frame duration on the GPU in [ns] + time_point_ns beginFrame; //!< Renderer::beginFrame() time since epoch [ns] + time_point_ns endFrame; //!< Renderer::endFrame() time since epoch [ns] + time_point_ns backendBeginFrame; //!< Backend thread time of frame start since epoch [ns] + time_point_ns backendEndFrame; //!< Backend thread time of frame end since epoch [ns] + time_point_ns gpuFrameComplete; //!< GPU thread time of frame end since epoch [ns] or 0 + time_point_ns vsync; //!< VSYNC time of this frame since epoch [ns] + time_point_ns displayPresent; //!< Actual presentation time of this frame since epoch [ns] + time_point_ns presentDeadline; //!< deadline for queuing a frame [ns] + duration_ns displayPresentInterval; //!< display refresh rate [ns] + duration_ns compositionToPresentLatency; //!< time between the start of composition and the expected present time [ns] + time_point_ns expectedPresentTime; //!< system's expected presentation time since epoch [ns] + }; + + /** + * Retrieve a history of frame timing information. The maximum frame history size is + * given by getMaxFrameHistorySize(). + * All or part of the history can be lost when using a different SwapChain in beginFrame(). + * @param historySize requested history size. The returned vector could be smaller. + * @return A vector of FrameInfo. + * @see beginFrame() + */ + utils::FixedCapacityVector getFrameInfoHistory( + size_t historySize = 1) const noexcept; + + /** + * @return the maximum supported frame history size. + * @see getFrameInfoHistory() + */ + size_t getMaxFrameHistorySize() const noexcept; + /** * Use FrameRateOptions to set the desired frame rate and control how quickly the system * reacts to GPU load changes. @@ -228,9 +274,42 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { /** - * Set-up a frame for this Renderer. + * The use of this method is optional. It sets the VSYNC time expressed as the duration in + * nanosecond since epoch of std::chrono::steady_clock. + * If called, passing 0 to vsyncSteadyClockTimeNano in Renderer::BeginFrame will use this + * time instead. + * @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock + * @see Engine::getSteadyClockTimeNano() + * @see Renderer::BeginFrame() + */ + void setVsyncTime(uint64_t steadyClockTimeNano) noexcept; + + /** + * Call skipFrame when momentarily skipping frames, for instance if the content of the + * scene doesn't change. + * + * @param vsyncSteadyClockTimeNano + */ + void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u); + + /** + * Returns true if the current frame should be rendered. + * + * This is a convenience method that returns the same value as beginFrame(). + * + * @return + * *false* the current frame should be skipped, + * *true* the current frame can be rendered + * + * @see + * beginFrame() + */ + bool shouldRenderFrame() const noexcept; + + /** + * Set up a frame for this Renderer. * - * beginFrame() manages frame pacing, and returns whether or not a frame should be drawn. The + * beginFrame() manages frame-pacing, and returns whether a frame should be drawn. The * goal of this is to skip frames when the GPU falls behind in order to keep the frame * latency low. * @@ -249,6 +328,8 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * or 0 if unknown. This value should be the timestamp of * the last h/w vsync. It is expressed in the * std::chrono::steady_clock time base. + * On Android this should be the frame time received from + * a Choreographer. * @param swapChain A pointer to the SwapChain instance to use. * * @return @@ -260,6 +341,8 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * * @note * All calls to render() must happen *after* beginFrame(). + * It is recommended to use the same swapChain for every call to beginFrame, failing to do + * so can result is losing all or part of the FrameInfo history. * * @see * endFrame() @@ -456,7 +539,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * * Framebuffer as seen on User buffer (PixelBufferDescriptor&) * screen - * + * * +--------------------+ * | | .stride .alignment * | | ----------------------->--> @@ -486,6 +569,9 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * uploaded to it via setImage, the data returned from readPixels will be y-flipped with respect * to the setImage call. * + * Note: the texture that backs the COLOR attachment for `renderTarget` must have + * TextureUsage::BLIT_SRC as part of its usage. + * * @remark * readPixels() is intended for debugging and testing. It will impact performance significantly. * @@ -577,6 +663,19 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { */ void resetUserTime(); + + /** + * Requests the next frameCount frames to be skipped. For Debugging. + * @param frameCount number of frames to skip. + */ + void skipNextFrames(size_t frameCount) const noexcept; + + /** + * Remainder count of frame to be skipped + * @return remaining frames to be skipped + */ + size_t getFrameToSkipCount() const noexcept; + protected: // prevent heap allocation ~Renderer() = default; diff --git a/package/android/libs/filament/include/filament/Scene.h b/package/android/libs/filament/include/filament/Scene.h index 9df6285c..8758ab70 100644 --- a/package/android/libs/filament/include/filament/Scene.h +++ b/package/android/libs/filament/include/filament/Scene.h @@ -141,6 +141,11 @@ class UTILS_PUBLIC Scene : public FilamentAPI { */ void removeEntities(const utils::Entity* UTILS_NONNULL entities, size_t count); + /** + * Remove all entities to the Scene. + */ + void removeAllEntities() noexcept; + /** * Returns the total number of Entities in the Scene, whether alive or not. * @return Total number of Entities in the Scene. diff --git a/package/android/libs/filament/include/filament/SkinningBuffer.h b/package/android/libs/filament/include/filament/SkinningBuffer.h index 36ae30ed..c397f62b 100644 --- a/package/android/libs/filament/include/filament/SkinningBuffer.h +++ b/package/android/libs/filament/include/filament/SkinningBuffer.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -39,7 +40,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { struct BuilderDetails; public: - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -69,6 +70,33 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { */ Builder& initialize(bool initialize = true) noexcept; + /** + * Associate an optional name with this SkinningBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this SkinningBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this SkinningBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this SkinningBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the SkinningBuffer object and returns a pointer to it. * diff --git a/package/android/libs/filament/include/filament/Skybox.h b/package/android/libs/filament/include/filament/Skybox.h index ce203aae..bacd0acb 100644 --- a/package/android/libs/filament/include/filament/Skybox.h +++ b/package/android/libs/filament/include/filament/Skybox.h @@ -131,6 +131,20 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { */ Builder& color(math::float4 color) noexcept; + /** + * Set the rendering priority of the Skybox. By default, it is set to the lowest + * priority (7) such that the Skybox is always rendered after the opaque objects, + * to reduce overdraw when depth culling is enabled. + * + * @param priority clamped to the range [0..7], defaults to 4; 7 is lowest priority + * (rendered last). + * + * @return Builder reference for chaining calls. + * + * @see RenderableManager::Builder::priority() + */ + Builder& priority(uint8_t priority) noexcept; + /** * Creates the Skybox object and returns a pointer to it. * diff --git a/package/android/libs/filament/include/filament/Stream.h b/package/android/libs/filament/include/filament/Stream.h index 6cafbacc..d3f914eb 100644 --- a/package/android/libs/filament/include/filament/Stream.h +++ b/package/android/libs/filament/include/filament/Stream.h @@ -23,6 +23,9 @@ #include #include +#include + +#include #include @@ -94,7 +97,7 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * * To create a NATIVE stream, call the
stream
method on the builder. */ - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -136,6 +139,33 @@ class UTILS_PUBLIC Stream : public FilamentAPI { */ Builder& height(uint32_t height) noexcept; + /** + * Associate an optional name with this Stream for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this Stream + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this Stream for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this Stream + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the Stream object and returns a pointer to it. * @@ -174,9 +204,10 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * The callback tales two arguments: the AHardwareBuffer and the userdata. * @param userdata Optional closure data. Filament will pass this into the callback when it * releases the image. + * @param transform Optional transform matrix to apply to the image. */ void setAcquiredImage(void* UTILS_NONNULL image, - Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata) noexcept; + Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata, math::mat3f const& transform = math::mat3f()) noexcept; /** * @see setAcquiredImage(void*, Callback, void*) @@ -187,10 +218,11 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * It callback tales two arguments: the AHardwareBuffer and the userdata. * @param userdata Optional closure data. Filament will pass this into the callback when it * releases the image. + * @param transform Optional transform matrix to apply to the image. */ void setAcquiredImage(void* UTILS_NONNULL image, backend::CallbackHandler* UTILS_NULLABLE handler, - Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata) noexcept; + Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata, math::mat3f const& transform = math::mat3f()) noexcept; /** * Updates the size of the incoming stream. Whether this value is used is diff --git a/package/android/libs/filament/include/filament/SwapChain.h b/package/android/libs/filament/include/filament/SwapChain.h index 6917507a..94721c0d 100644 --- a/package/android/libs/filament/include/filament/SwapChain.h +++ b/package/android/libs/filament/include/filament/SwapChain.h @@ -21,7 +21,6 @@ #include #include -#include #include #include @@ -35,7 +34,7 @@ class Engine; /** * A swap chain represents an Operating System's *native* renderable surface. * - * Typically it's a native window or a view. Because a SwapChain is initialized from a + * Typically, it's a native window or a view. Because a SwapChain is initialized from a * native object, it is given to filament as a `void *`, which must be of the proper type * for each platform filament is running on. * @@ -158,7 +157,7 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { /** * Requests a SwapChain with an alpha channel. */ - static const uint64_t CONFIG_TRANSPARENT = backend::SWAP_CHAIN_CONFIG_TRANSPARENT; + static constexpr uint64_t CONFIG_TRANSPARENT = backend::SWAP_CHAIN_CONFIG_TRANSPARENT; /** * This flag indicates that the swap chain may be used as a source surface @@ -168,13 +167,13 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * @see * Renderer.copyFrame() */ - static const uint64_t CONFIG_READABLE = backend::SWAP_CHAIN_CONFIG_READABLE; + static constexpr uint64_t CONFIG_READABLE = backend::SWAP_CHAIN_CONFIG_READABLE; /** * Indicates that the native X11 window is an XCB window rather than an XLIB window. * This is ignored on non-Linux platforms and in builds that support only one X11 API. */ - static const uint64_t CONFIG_ENABLE_XCB = backend::SWAP_CHAIN_CONFIG_ENABLE_XCB; + static constexpr uint64_t CONFIG_ENABLE_XCB = backend::SWAP_CHAIN_CONFIG_ENABLE_XCB; /** * Indicates that the native window is a CVPixelBufferRef. @@ -186,7 +185,7 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * Filament. Filament will call CVPixelBufferRetain during Engine::createSwapChain, and * CVPixelBufferRelease when the swap chain is destroyed. */ - static const uint64_t CONFIG_APPLE_CVPIXELBUFFER = + static constexpr uint64_t CONFIG_APPLE_CVPIXELBUFFER = backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER; /** @@ -234,6 +233,21 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { */ static constexpr uint64_t CONFIG_PROTECTED_CONTENT = backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; + /** + * Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the + * given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is + * true. + * + * This is supported by EGL(Android) and Metal. Other GL platforms (GLX, WGL, etc) don't support + * it because the swapchain MSAA settings must be configured before window creation. + * + * With Metal, this flag should only be used when rendering a single View into a SwapChain. This + * flag is not supported when rendering multiple Filament Views into this SwapChain. + * + * @see isMSAASwapChainSupported(4) + */ + static constexpr uint64_t CONFIG_MSAA_4_SAMPLES = backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES; + /** * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag. * The default implementation returns false. @@ -252,56 +266,91 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { */ static bool isSRGBSwapChainSupported(Engine& engine) noexcept; + /** + * Return whether createSwapChain supports the CONFIG_MSAA_*_SAMPLES flag. + * The default implementation returns false. + * + * @param engine A pointer to the filament Engine + * @param samples The number of samples + * @return true if CONFIG_MSAA_*_SAMPLES is supported, false otherwise. + */ + static bool isMSAASwapChainSupported(Engine& engine, uint32_t samples) noexcept; + void* UTILS_NULLABLE getNativeWindow() const noexcept; /** - * FrameScheduledCallback is a callback function that notifies an application when Filament has - * completed processing a frame and that frame is ready to be scheduled for presentation. + * If this flag is passed to setFrameScheduledCallback, then the behavior of the default + * CallbackHandler (when nullptr is passed as the handler argument) is altered to call the + * callback on the Metal completion handler thread (as opposed to the main Filament thread). + * This flag also instructs the Metal backend to release the associated CAMetalDrawable on the + * completion handler thread. + * + * This flag has no effect if a custom CallbackHandler is passed or on backends other than Metal. + * + * @see setFrameScheduledCallback + */ + static constexpr uint64_t CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER = 1; + + /** + * FrameScheduledCallback is a callback function that notifies an application about the status + * of a frame after Filament has finished its processing. + * + * The exact timing and semantics of this callback differ depending on the graphics backend in + * use. + * + * Metal Backend + * ============= + * + * With the Metal backend, this callback signifies that Filament has completed all CPU-side + * processing for a frame and the frame is ready to be scheduled for presentation. * * Typically, Filament is responsible for scheduling the frame's presentation to the SwapChain. - * If a SwapChain::FrameScheduledCallback is set, however, the application bares the - * responsibility of scheduling a frame for presentation by calling the backend::PresentCallable - * passed to the callback function. Currently this functionality is only supported by the Metal - * backend. + * If a SwapChain::FrameScheduledCallback is set, however, the application bears the + * responsibility of scheduling the frame for presentation by calling the + * backend::PresentCallable passed to the callback function. In this mode, Filament will *not* + * automatically schedule the frame for presentation. * - * A FrameScheduledCallback can be set on an individual SwapChain through - * SwapChain::setFrameScheduledCallback. If the callback is set for a given frame, then the - * SwapChain will *not* automatically schedule itself for presentation. Instead, the application - * must call the PresentCallable passed to the FrameScheduledCallback. + * When using the Metal backend, if your application delays the call to the PresentCallable + * (e.g., by invoking it on a separate thread), you must ensure all PresentCallables have been + * called before shutting down the Filament Engine. You can guarantee this by calling + * Engine::flushAndWait() before Engine::shutdown(). This is necessary to ensure the Engine has + * a chance to clean up all memory related to frame presentation. * - * Each SwapChain can have only one FrameScheduledCallback set per frame. If - * setFrameScheduledCallback is called multiple times on the same SwapChain before - * Renderer::endFrame(), the most recent call effectively overwrites any previously set - * callback. This allows the callback to be updated as needed before the frame has finished - * encoding. + * Other Backends (OpenGL, Vulkan, WebGPU) + * ======================================= * - * The "last" callback set by setFrameScheduledCallback gets "latched" when Renderer::endFrame() - * is executed. At this point, the state of the callback is fixed and is the one used for the - * frame that was just encoded. Subsequent changes to the callback using - * setFrameScheduledCallback after endFrame() apply to the next frame. + * On other backends, this callback serves as a notification that Filament has completed all + * CPU-side processing for a frame. Filament proceeds with its normal presentation logic + * automatically, and the PresentCallable passed to the callback is a no-op that can be safely + * ignored. * - * Use \c setFrameScheduledCallback() (with default arguments) to unset the callback. + * General Behavior + * ================ * - * If your application delays the call to the PresentCallable by, for example, calling it on a - * separate thread, you must ensure all PresentCallables have been called before shutting down - * the Filament Engine. You can do this by issuing an Engine::flushAndWait before calling - * Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean - * up all memory related to frame presentation. + * A FrameScheduledCallback can be set on an individual SwapChain through + * SwapChain::setFrameScheduledCallback. Each SwapChain can have only one callback set per + * frame. If setFrameScheduledCallback is called multiple times on the same SwapChain before + * Renderer::endFrame(), the most recent call effectively overwrites any previously set + * callback. * - * @param handler Handler to dispatch the callback or nullptr for the default handler. - * @param callback Callback called when the frame is scheduled. + * The callback set by setFrameScheduledCallback is "latched" when Renderer::endFrame() is + * executed. At this point, the callback is fixed for the frame that was just encoded. + * Subsequent calls to setFrameScheduledCallback after endFrame() will apply to the next frame. + * + * Use \c setFrameScheduledCallback() (with default arguments) to unset the callback. * - * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other - * backends ignore the callback (which will never be called) and proceed normally. + * @param handler Handler to dispatch the callback or nullptr for the default handler. + * @param callback Callback to be invoked when the frame processing is complete. + * @param flags See CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER * * @see CallbackHandler * @see PresentCallable */ void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, - FrameScheduledCallback&& callback = {}); + FrameScheduledCallback&& callback = {}, uint64_t flags = 0); /** - * Returns whether or not this SwapChain currently has a FrameScheduledCallback set. + * Returns whether this SwapChain currently has a FrameScheduledCallback set. * * @return true, if the last call to setFrameScheduledCallback set a callback * diff --git a/package/android/libs/filament/include/filament/Sync.h b/package/android/libs/filament/include/filament/Sync.h new file mode 100644 index 00000000..0398ced6 --- /dev/null +++ b/package/android/libs/filament/include/filament/Sync.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_SYNC_H +#define TNT_FILAMENT_SYNC_H + +#include + +#include +#include +#include + +namespace filament { + +class UTILS_PUBLIC Sync : public FilamentAPI { +public: + using CallbackHandler = backend::CallbackHandler; + using Callback = backend::Platform::SyncCallback; + + /** + * Fetches a handle to the external, platform-specific representation of + * this sync object. + * + * @param handler A handler for the callback that will receive the handle + * @param callback A callback that will receive the handle when ready + * @param userData Data to be passed to the callback so that the application + * can identify what frame the sync is relevant to. + * @return The external handle for the Sync. This is valid destroy() is + * called on this Sync object. + */ + void getExternalHandle(CallbackHandler* handler, Callback callback, void* userData) noexcept; + +protected: + // prevent heap allocation + ~Sync() = default; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_SYNC_H diff --git a/package/android/libs/filament/include/filament/Texture.h b/package/android/libs/filament/include/filament/Texture.h index 8a27f831..f7d3fcbf 100644 --- a/package/android/libs/filament/include/filament/Texture.h +++ b/package/android/libs/filament/include/filament/Texture.h @@ -23,11 +23,15 @@ #include #include +#include #include +#include +#include #include +#include #include #include @@ -70,7 +74,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { struct BuilderDetails; public: - static constexpr const size_t BASE_LEVEL = 0; + static constexpr size_t BASE_LEVEL = 0; //! Face offsets for all faces of a cubemap struct FaceOffsets; @@ -84,35 +88,42 @@ class UTILS_PUBLIC Texture : public FilamentAPI { using CompressedType = backend::CompressedPixelDataType; //!< Compressed pixel data format using Usage = backend::TextureUsage; //!< Usage affects texel layout using Swizzle = backend::TextureSwizzle; //!< Texture swizzle + using ExternalImageHandle = backend::Platform::ExternalImageHandle; + using ExternalImageHandleRef = backend::Platform::ExternalImageHandleRef; + using AsyncCompletionCallback = + std::function; + using AsyncCallId = backend::AsyncCallId; - /** @return whether a backend supports a particular format. */ + /** @return Whether a backend supports a particular format. */ static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept; - /** @return whether this backend supports protected textures. */ + /** @return Whether a backend supports mipmapping of a particular format. */ + static bool isTextureFormatMipmappable(Engine& engine, InternalFormat format) noexcept; + + /** @return Whether particular format is compressed */ + static bool isTextureFormatCompressed(InternalFormat format) noexcept; + + /** @return Whether this backend supports protected textures. */ static bool isProtectedTexturesSupported(Engine& engine) noexcept; - /** @return whether a backend supports texture swizzling. */ + /** @return Whether a backend supports texture swizzling. */ static bool isTextureSwizzleSupported(Engine& engine) noexcept; - static size_t computeTextureDataSize(Texture::Format format, Texture::Type type, + static size_t computeTextureDataSize(Format format, Type type, size_t stride, size_t height, size_t alignment) noexcept; + /** @return Whether a combination of texture format, pixel format and type is valid. */ + static bool validatePixelFormatAndType(InternalFormat internalFormat, Format format, Type type) noexcept; - /** - * Options for environment prefiltering into reflection map - * - * @see generatePrefilterMipmap() - */ - struct PrefilterOptions { - uint16_t sampleCount = 8; //!< sample count used for filtering - bool mirror = true; //!< whether the environment must be mirrored - private: - UTILS_UNUSED uintptr_t reserved[3] = {}; - }; + /** @return the maximum size in texels of a texture of type \p type. At least 2048 for + * 2D textures, 256 for 3D textures. */ + static size_t getMaxTextureSize(Engine& engine, Sampler type) noexcept; + /** @return the maximum number of layers supported by texture arrays. At least 256. */ + static size_t getMaxArrayTextureLayers(Engine& engine) noexcept; //! Use Builder to construct a Texture object instance - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -156,6 +167,20 @@ class UTILS_PUBLIC Texture : public FilamentAPI { */ Builder& levels(uint8_t levels) noexcept; + /** + * Specifies the numbers of samples used for MSAA (Multisample Anti-Aliasing). + * + * Calling this method implicitly indicates the texture is used as a render target. Hence, + * this method should not be used in conjunction with other methods that are semantically + * conflicting like `setImage`. + * + * If this is invoked for array textures, it means this texture is used for multiview. + * + * @param samples Number of samples for this texture. + * @return This Builder, for chaining calls. + */ + Builder& samples(uint8_t samples) noexcept; + /** * Specifies the type of sampler to use. * @param target Sampler type @@ -202,6 +227,71 @@ class UTILS_PUBLIC Texture : public FilamentAPI { */ Builder& swizzle(Swizzle r, Swizzle g, Swizzle b, Swizzle a) noexcept; + /** + * Associate an optional name with this Texture for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this Texture + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this Texture for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this Texture + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + + /** + * Creates an external texture. The content must be set using setExternalImage(). + * The sampler can be SAMPLER_EXTERNAL or SAMPLER_2D depending on the format. Generally + * YUV formats must use SAMPLER_EXTERNAL. This depends on the backend features and is not + * validated. + * + * If the Sampler is set to SAMPLER_EXTERNAL, external() is implied. + * + * @return + */ + Builder& external() noexcept; + + /** + * Specifies a callback that will execute once the resource's data has been fully allocated + * within the GPU memory. This enables the resource creation process to be handled + * asynchronously. + * + * Any asynchronous calls made during a resource's asynchronous creation (using this method) + * are safe because they are queued and executed in sequence. However, invoking regular + * methods on the same resource before it's fully ready is unsafe and may cause undefined + * behavior. Users can call the `isCreationComplete()` method for the resource to confirm + * when the resource is ready for regular API calls. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * This method and the `external()` method are mutually exclusive. You cannot use both + * because external texture's contents are filled later by calling `setExternalImage()`. + * + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * @return This Builder, for chaining calls. + */ + Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback = nullptr, + void* UTILS_NULLABLE user = nullptr) noexcept; + /** * Creates the Texture object and returns a pointer to it. * @@ -332,7 +422,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * uint32_t width, uint32_t height, uint32_t depth, * PixelBufferDescriptor&& buffer) */ - inline void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const { + void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const { setImage(engine, level, 0, 0, 0, uint32_t(getWidth(level)), uint32_t(getHeight(level)), 1, std::move(buffer)); } @@ -345,7 +435,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * uint32_t width, uint32_t height, uint32_t depth, * PixelBufferDescriptor&& buffer) */ - inline void setImage(Engine& engine, size_t level, + void setImage(Engine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, PixelBufferDescriptor&& buffer) const { setImage(engine, level, xoffset, yoffset, 0, width, height, 1, std::move(buffer)); @@ -378,9 +468,108 @@ class UTILS_PUBLIC Texture : public FilamentAPI { void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const; + /** + * An asynchronous version of `setImage()`. + * Updates a sub-image of a 3D texture or 2D texture array for a level. Cubemaps are treated + * like a 2D array of six layers. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param engine Engine this texture is associated to. + * @param level Level to set the image for. + * @param xoffset Left offset of the sub-region to update. + * @param yoffset Bottom offset of the sub-region to update. + * @param zoffset Depth offset of the sub-region to update. + * @param width Width of the sub-region to update. + * @param height Height of the sub-region to update. + * @param depth Depth of the sub-region to update. + * @param buffer Client-side buffer containing the image to set. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + * + * @attention \p engine must be the instance passed to Builder::build() + * @attention \p level must be less than getLevels(). + * @attention \p buffer's Texture::Format must match that of getFormat(). + * @attention This Texture instance must use Sampler::SAMPLER_3D, Sampler::SAMPLER_2D_ARRAY + * or Sampler::SAMPLER_CUBEMAP. + * + * @see Builder::sampler() + */ + AsyncCallId setImageAsync(Engine& engine, size_t level, + uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + uint32_t width, uint32_t height, uint32_t depth, + PixelBufferDescriptor&& buffer, + backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr) const; + + /** + * inline helper to update a 2D texture asynchronously + * + * @see setImageAsync(Engine& engine, size_t level, + * uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + * uint32_t width, uint32_t height, uint32_t depth, + * PixelBufferDescriptor&& buffer, + * backend::CallbackHandler* UTILS_NULLABLE handler, + * AsyncCompletionCallback callback, void* user) + */ + AsyncCallId setImageAsync(Engine& engine, size_t level, PixelBufferDescriptor&& buffer, + backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr) const { + return setImageAsync(engine, level, 0, 0, 0, + uint32_t(getWidth(level)), uint32_t(getHeight(level)), 1, std::move(buffer), + handler, std::move(callback), user); + } + + /** + * inline helper to update a 2D texture asynchronously + * + * @see setImageAsync(Engine& engine, size_t level, + * uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + * uint32_t width, uint32_t height, uint32_t depth, + * PixelBufferDescriptor&& buffer, + * backend::CallbackHandler* UTILS_NULLABLE handler, + * AsyncCompletionCallback callback, void* user) + */ + AsyncCallId setImageAsync(Engine& engine, size_t level, + uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, + PixelBufferDescriptor&& buffer, + backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr) const { + return setImageAsync(engine, level, xoffset, yoffset, 0, width, height, 1, std::move(buffer), + handler, std::move(callback), user); + } + + /** + * Specify the external image to associate with this Texture. Typically, the external + * image is OS specific, and can be a video or camera frame. + * There are many restrictions when using an external image as a texture, such as: + * - only the level of detail (lod) 0 can be specified + * - only nearest or linear filtering is supported + * - the size and format of the texture is defined by the external image + * - only the CLAMP_TO_EDGE wrap mode is supported + * + * @param engine Engine this texture is associated to. + * @param image An opaque handle to a platform specific image. It must be created using Platform + * specific APIs. For example PlatformEGL::createExternalImage(EGLImageKHR eglImage) + * + * @see PlatformEGL::createExternalImage + * @see PlatformEGLAndroid::createExternalImage + * @see PlatformCocoaGL::createExternalImage + * @see PlatformCocoaTouchGL::createExternalImage + */ + void setExternalImage(Engine& engine, ExternalImageHandleRef image) noexcept; /** - * Specify the external image to associate with this Texture. Typically the external + * Specify the external image to associate with this Texture. Typically, the external * image is OS specific, and can be a video or camera frame. * There are many restrictions when using an external image as a texture, such as: * - only the level of detail (lod) 0 can be specified @@ -401,11 +590,13 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * * @see Builder::sampler() * + * @deprecated Instead, use setExternalImage(Engine& engine, ExternalImageHandleRef image) */ + UTILS_DEPRECATED void setExternalImage(Engine& engine, void* UTILS_NONNULL image) noexcept; /** - * Specify the external image and plane to associate with this Texture. Typically the external + * Specify the external image and plane to associate with this Texture. Typically, the external * image is OS specific, and can be a video or camera frame. When using this method, the * external image must be a planar type (such as a YUV camera frame). The plane parameter * selects which image plane is bound to this texture. @@ -436,7 +627,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { void setExternalImage(Engine& engine, void* UTILS_NONNULL image, size_t plane) noexcept; /** - * Specify the external stream to associate with this Texture. Typically the external + * Specify the external stream to associate with this Texture. Typically, the external * stream is OS specific, and can be a video or camera stream. * There are many restrictions when using an external stream as a texture, such as: * - only the level of detail (lod) 0 can be specified @@ -447,7 +638,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @param stream A Stream object * * @attention \p engine must be the instance passed to Builder::build() - * @attention This Texture instance must use Sampler::SAMPLER_EXTERNAL or it has no effect + * @attention This Texture instance must use Sampler::SAMPLER_EXTERNAL, or it has no effect * * @see Builder::sampler(), Stream * @@ -467,43 +658,16 @@ class UTILS_PUBLIC Texture : public FilamentAPI { void generateMipmaps(Engine& engine) const noexcept; /** - * Creates a reflection map from an environment map. - * - * This is a utility function that replaces calls to Texture::setImage(). - * The provided environment map is processed and all mipmap levels are populated. The - * processing is similar to the offline tool `cmgen` as a lower quality setting. - * - * This function is intended to be used when the environment cannot be processed offline, - * for instance if it's generated at runtime. - * - * The source data must obey to some constraints: - * - the data type must be PixelDataFormat::RGB - * - the data format must be one of - * - PixelDataType::FLOAT - * - PixelDataType::HALF - * - * The current texture must be a cubemap - * - * The reflections cubemap's internal format cannot be a compressed format. - * - * The reflections cubemap's dimension must be a power-of-two. + * This non-blocking method checks if the resource has finished creation. If the resource + * creation was initiated asynchronously, it will return true only after all related + * asynchronous tasks are complete. If the resource was created normally without using async + * method, it will always return true. * - * @warning This operation is computationally intensive, especially with large environments and - * is currently synchronous. Expect about 1ms for a 16x16 cubemap. - * - * @param engine Reference to the filament::Engine to associate this IndirectLight with. - * @param buffer Client-side buffer containing the images to set. - * @param faceOffsets Offsets in bytes into \p buffer for all six images. The offsets - * are specified in the following order: +x, -x, +y, -y, +z, -z - * @param options Optional parameter to controlling user-specified quality and options. - * - * @exception utils::PreConditionPanic if the source data constraints are not respected. + * @return Whether the resource is created. * + * @see Builder::async() */ - void generatePrefilterMipmap(Engine& engine, - PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, - PrefilterOptions const* UTILS_NULLABLE options = nullptr); - + bool isCreationComplete() const noexcept; /** @deprecated */ struct FaceOffsets { diff --git a/package/android/libs/filament/include/filament/TextureSampler.h b/package/android/libs/filament/include/filament/TextureSampler.h index ba5e5534..ea40a705 100644 --- a/package/android/libs/filament/include/filament/TextureSampler.h +++ b/package/android/libs/filament/include/filament/TextureSampler.h @@ -154,8 +154,8 @@ class UTILS_PUBLIC TextureSampler { /** * This controls anisotropic filtering. - * @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 0. - * The maximum permissible value is 7. + * @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 1. + * The maximum permissible value is 128. */ void setAnisotropy(float anisotropy) noexcept { const int log2 = ilogbf(anisotropy > 0 ? anisotropy : -anisotropy); diff --git a/package/android/libs/filament/include/filament/ToneMapper.h b/package/android/libs/filament/include/filament/ToneMapper.h index e89e702c..cff0576e 100644 --- a/package/android/libs/filament/include/filament/ToneMapper.h +++ b/package/android/libs/filament/include/filament/ToneMapper.h @@ -68,6 +68,21 @@ struct UTILS_PUBLIC ToneMapper { * function applied ("linear") */ virtual math::float3 operator()(math::float3 c) const noexcept = 0; + + /** + * If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b)) + * + * This may be used to indicate that the color grading's LUT only requires a 1D texture instead + * of a 3D texture, potentially saving a significant amount of memory and generation time. + */ + virtual bool isOneDimensional() const noexcept { return false; } + + /** + * True if this tonemapper only works in low-dynamic-range. + * + * This may be used to indicate that the color grading's LUT doesn't need to be log encoded. + */ + virtual bool isLDR() const noexcept { return false; } }; /** @@ -79,6 +94,8 @@ struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper { ~LinearToneMapper() noexcept final; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return true; } + bool isLDR() const noexcept override { return true; } }; /** @@ -91,6 +108,8 @@ struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper { ~ACESToneMapper() noexcept final; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; /** @@ -104,6 +123,8 @@ struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper { ~ACESLegacyToneMapper() noexcept final; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; /** @@ -117,6 +138,8 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { ~FilmicToneMapper() noexcept final; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return true; } + bool isLDR() const noexcept override { return false; } }; /** @@ -129,6 +152,8 @@ struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper { ~PBRNeutralToneMapper() noexcept final; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; /** @@ -150,6 +175,8 @@ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper { ~AgxToneMapper() noexcept final; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } AgxLook look; }; @@ -194,6 +221,8 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { GenericToneMapper& operator=(GenericToneMapper&& rhs) noexcept; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return true; } + bool isLDR() const noexcept override { return false; } /** Returns the contrast of the curve as a strictly positive value. */ float getContrast() const noexcept; @@ -256,6 +285,8 @@ struct UTILS_PUBLIC DisplayRangeToneMapper final : public ToneMapper { ~DisplayRangeToneMapper() noexcept override; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; } // namespace filament diff --git a/package/android/libs/filament/include/filament/VertexBuffer.h b/package/android/libs/filament/include/filament/VertexBuffer.h index fccbd004..b29c7f9a 100644 --- a/package/android/libs/filament/include/filament/VertexBuffer.h +++ b/package/android/libs/filament/include/filament/VertexBuffer.h @@ -26,7 +26,9 @@ #include #include +#include +#include #include #include @@ -60,8 +62,12 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { public: using AttributeType = backend::ElementType; using BufferDescriptor = backend::BufferDescriptor; + using AsyncCompletionCallback = + std::function; + using AsyncCallId = backend::AsyncCallId; - class Builder : public BuilderBase { + + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -158,6 +164,56 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { */ Builder& advancedSkinning(bool enabled) noexcept; + /** + * Associate an optional name with this VertexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this VertexBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this VertexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this VertexBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + + /** + * Specifies a callback that will execute once the resource's data has been fully allocated + * within the GPU memory. This enables the resource creation process to be handled + * asynchronously. + * + * Any asynchronous calls made during a resource's asynchronous creation (using this method) + * are safe because they are queued and executed in sequence. However, invoking regular + * methods on the same resource before it's fully ready is unsafe and may cause undefined + * behavior. Users can call the `isCreationComplete()` method for the resource to confirm + * when the resource is ready for regular API calls. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * @return This Builder, for chaining calls. + */ + Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback = nullptr, + void* UTILS_NULLABLE user = nullptr) noexcept; + /** * Creates the VertexBuffer object and returns a pointer to it. * @@ -182,7 +238,7 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { size_t getVertexCount() const noexcept; /** - * Asynchronously copy-initializes the specified buffer from the given buffer data. + * copy-initializes the specified buffer from the given buffer data. * * Do not use this if you called enableBufferObjects() on the Builder. * @@ -193,11 +249,41 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { * index \p bufferIndex. BufferDescriptor points to raw, untyped data that will * be copied as-is into the buffer. * @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex - * buffer set. + * buffer set. Must be multiple of 4. */ void setBufferAt(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + /** + * An asynchronous version of `setBufferAt()`. + * Asynchronously copy-initializes the specified buffer from the given buffer data. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * Do not use this if you called enableBufferObjects() on the Builder. + * + * @param engine Reference to the filament::Engine to associate this VertexBuffer with. + * @param bufferIndex Index of the buffer to initialize. Must be between 0 + * and Builder::bufferCount() - 1. + * @param buffer A BufferDescriptor representing the data used to initialize the buffer at + * index \p bufferIndex. BufferDescriptor points to raw, untyped data that will + * be copied as-is into the buffer. + * @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex + * buffer set. Must be multiple of 4. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + */ + AsyncCallId setBufferAtAsync(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer, + uint32_t byteOffset, backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, void* UTILS_NULLABLE user = nullptr); + /** * Swaps in the given buffer object. * @@ -211,6 +297,45 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { void setBufferObjectAt(Engine& engine, uint8_t bufferIndex, BufferObject const* UTILS_NONNULL bufferObject); + /** + * An asynchronous version of `setBufferObjectAt()`. + * Swaps in the given buffer object. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * To use this, you must first call enableBufferObjects() on the Builder. + * + * @param engine Reference to the filament::Engine to associate this VertexBuffer with. + * @param bufferIndex Index of the buffer to initialize. Must be between 0 + * and Builder::bufferCount() - 1. + * @param bufferObject The handle to the GPU data that will be used in this buffer slot. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + */ + AsyncCallId setBufferObjectAtAsync(Engine& engine, uint8_t bufferIndex, + BufferObject const* UTILS_NONNULL bufferObject, + backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, void* UTILS_NULLABLE user = nullptr); + + /** + * This non-blocking method checks if the resource has finished creation. If the resource + * creation was initiated asynchronously, it will return true only after all related + * asynchronous tasks are complete. If the resource was created normally without using async + * method, it will always return true. + * + * @return Whether the resource is created. + * + * @see Builder::async() + */ + bool isCreationComplete() const noexcept; + protected: // prevent heap allocation ~VertexBuffer() = default; diff --git a/package/android/libs/filament/include/filament/View.h b/package/android/libs/filament/include/filament/View.h index 1a1ed96f..0240bafa 100644 --- a/package/android/libs/filament/include/filament/View.h +++ b/package/android/libs/filament/include/filament/View.h @@ -24,8 +24,10 @@ #include #include +#include #include +#include #include @@ -40,6 +42,7 @@ class CallbackHandler; class Camera; class ColorGrading; +class Engine; class MaterialInstance; class RenderTarget; class Scene; @@ -182,12 +185,13 @@ class UTILS_PUBLIC View : public FilamentAPI { * View.\n * The View doesn't take ownership of the Camera pointer (which * acts as a reference). + * If the camera isn't set, Renderer::render() will result in a no-op. * * @note * There is no reference-counting. * Make sure to dissociate a Camera from all Views before destroying it. */ - void setCamera(Camera* UTILS_NONNULL camera) noexcept; + void setCamera(Camera* UTILS_NULLABLE camera) noexcept; /** * Returns whether a Camera is set. @@ -198,18 +202,38 @@ class UTILS_PUBLIC View : public FilamentAPI { /** * Returns the Camera currently associated with this View. - * @return A reference to the Camera associated to this View. + * Undefined behavior if hasCamera() is false. + * @return A reference to the Camera associated to this View if hasCamera() is true. + * @see hasCamera() */ Camera& getCamera() noexcept; /** * Returns the Camera currently associated with this View. + * Undefined behavior if hasCamera() is false. * @return A reference to the Camera associated to this View. + * @see hasCamera() */ Camera const& getCamera() const noexcept { return const_cast(this)->getCamera(); } + + /** + * Sets whether a channel must clear the depth buffer before all primitives are rendered. + * Channel depth clear is off by default for all channels. + * This is orthogonal to Renderer::setClearOptions(). + * @param channel between 0 and 7 + * @param enabled true to enable clear, false to disable + */ + void setChannelDepthClearEnabled(uint8_t channel, bool enabled) noexcept; + + /** + * @param channel between 0 and 7 + * @return true if this channel has depth clear enabled. + */ + bool isChannelDepthClearEnabled(uint8_t channel) const noexcept; + /** * Sets the blending mode used to draw the view into the SwapChain. * @@ -517,6 +541,14 @@ class UTILS_PUBLIC View : public FilamentAPI { */ DynamicResolutionOptions getDynamicResolutionOptions() const noexcept; + /** + * Returns the last dynamic resolution scale factor used by this view. This value is updated + * when Renderer::render(View*) is called + * @return a float2 where x is the horizontal and y the vertical scale factor. + * @see Renderer::render + */ + math::float2 getLastDynamicResolutionScale() const noexcept; + /** * Sets the rendering quality for this view. Refer to RenderQuality for more * information about the different settings available. @@ -540,12 +572,13 @@ class UTILS_PUBLIC View : public FilamentAPI { * visible -- in this case, using a larger value can improve performance. * e.g. when standing and looking straight, several meters of the ground * isn't visible and if lights are expected to shine there, there is no - * point using a short zLightNear. (Default 5m). + * point using a short zLightNear. This value is clamped between + * the camera near and far plane. (Default 5m). * * @param zLightFar Distance from the camera after which lights are not expected to be visible. * Similarly to zLightNear, setting this value properly can improve - * performance. (Default 100m). - * + * performance. This value is clamped between the camera near and far plane. + * (Default 100m). * * Together zLightNear and zLightFar must be chosen so that the visible influence of lights * is spread between these two values. @@ -568,6 +601,13 @@ class UTILS_PUBLIC View : public FilamentAPI { */ void setShadowType(ShadowType shadow) noexcept; + /** + * Returns the shadow mapping technique used by this View. + * + * @return value set by setShadowType(). + */ + ShadowType getShadowType() const noexcept; + /** * Sets VSM shadowing options that apply across the entire View. * @@ -659,6 +699,26 @@ class UTILS_PUBLIC View : public FilamentAPI { */ bool isFrontFaceWindingInverted() const noexcept; + /** + * Enables or disables transparent picking. Disabled by default. + * + * When transparent picking is enabled, View::pick() will pick from both + * transparent and opaque renderables. When disabled, View::pick() will only + * pick from opaque renderables. + * + * @param enabled true enables transparent picking, false disables it. + * + * @note Transparent picking will create an extra pass for rendering depth + * from both transparent and opaque renderables. + */ + void setTransparentPickingEnabled(bool enabled) noexcept; + + /** + * Returns true if transparent picking is enabled. + * See setTransparentPickingEnabled() for more information. + */ + bool isTransparentPickingEnabled() const noexcept; + /** * Enables use of the stencil buffer. * @@ -726,8 +786,31 @@ class UTILS_PUBLIC View : public FilamentAPI { void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept; //! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing. - Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept; + utils::FixedCapacityVector getDirectionalShadowCameras() const noexcept; + + //! debugging: enable or disable froxel visualisation for this view. + void setFroxelVizEnabled(bool enabled) noexcept; + + //! debugging: returns information about the froxel configuration + struct FroxelConfigurationInfo { + uint16_t width; + uint16_t height; + uint16_t depth; + uint32_t viewportWidth; + uint32_t viewportHeight; + math::uint2 froxelDimension; + float zLightFar; + float linearizer; + math::mat4f p; + math::float4 clipTransform; + }; + + struct FroxelConfigurationInfoWithAge { + FroxelConfigurationInfo info; + uint32_t age; + }; + FroxelConfigurationInfoWithAge getFroxelConfigurationInfo() const noexcept; /** Result of a picking query */ struct PickingQueryResult { @@ -878,6 +961,17 @@ class UTILS_PUBLIC View : public FilamentAPI { */ 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/package/android/libs/filament/include/geometry/TangentSpaceMesh.h b/package/android/libs/filament/include/geometry/TangentSpaceMesh.h index 980e81ac..1872aaad 100644 --- a/package/android/libs/filament/include/geometry/TangentSpaceMesh.h +++ b/package/android/libs/filament/include/geometry/TangentSpaceMesh.h @@ -340,7 +340,7 @@ class TangentSpaceMesh { std::is_same::value || std::is_same::value>::type; template> - void getAux(AuxAttribute attribute, T* out, size_t stride = 0) const noexcept; + void getAux(AuxAttribute attribute, T* out, size_t stride = 0) const; /** * Get number of output triangles. diff --git a/package/android/libs/filament/include/gltfio/MaterialProvider.h b/package/android/libs/filament/include/gltfio/MaterialProvider.h index f62d667b..e4cf01b3 100644 --- a/package/android/libs/filament/include/gltfio/MaterialProvider.h +++ b/package/android/libs/filament/include/gltfio/MaterialProvider.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -93,10 +94,18 @@ struct alignas(4) MaterialKey { bool hasSheen : 1; bool hasIOR : 1; bool hasVolume : 1; - uint8_t padding : 5; + bool hasDispersion : 1; + bool hasSpecular : 1; + bool hasSpecularTexture : 1; + bool hasSpecularColorTexture : 1; + bool padding : 1; + // -- 32 bit boundary -- + uint8_t specularTextureUV; + uint8_t specularColorTextureUV; + uint16_t padding2; }; -static_assert(sizeof(MaterialKey) == 16, "MaterialKey has unexpected size."); +static_assert(sizeof(MaterialKey) == 20, "MaterialKey has unexpected size."); UTILS_WARNING_POP @@ -189,6 +198,7 @@ void processShaderString(std::string* shader, const UvMap& uvmap, * Creates a material provider that builds materials on the fly, composing GLSL at run time. * * @param optimizeShaders Optimizes shaders, but at significant cost to construction time. + * @param variantFilters Filter out variants that are not required. * @return New material provider that can build materials at run time. * * Requires \c libfilamat to be linked in. Not available in \c libgltfio_core. @@ -196,7 +206,8 @@ void processShaderString(std::string* shader, const UvMap& uvmap, * @see createUbershaderProvider */ UTILS_PUBLIC -MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders = false); +MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders = false, + utils::FixedCapacityVector const& variantFilters = {}); /** * Creates a material provider that loads a small set of pre-built materials. diff --git a/package/android/libs/filament/include/gltfio/TextureProvider.h b/package/android/libs/filament/include/gltfio/TextureProvider.h index ff497af2..0015c86a 100644 --- a/package/android/libs/filament/include/gltfio/TextureProvider.h +++ b/package/android/libs/filament/include/gltfio/TextureProvider.h @@ -179,6 +179,19 @@ TextureProvider* createStbProvider(filament::Engine* engine); */ TextureProvider* createKtx2Provider(filament::Engine* engine); +/** + * If webp support is enabled at build time, creates a decoder that can handle "image/webp" + * lossless and lossy content. + * If webp support is not enabled at build time, returns nullptr. + */ +TextureProvider* createWebpProvider(filament::Engine* engine); + +/** + * Indicates if build-time webp support was included. + * Returns true if it was and false if not. + */ +bool isWebpSupported(); + } // namespace filament::gltfio template<> struct utils::EnableBitMaskOperators diff --git a/package/android/libs/filament/include/gltfio/materials/uberarchive.h b/package/android/libs/filament/include/gltfio/materials/uberarchive.h index b36ddd36..ca85fa42 100644 --- a/package/android/libs/filament/include/gltfio/materials/uberarchive.h +++ b/package/android/libs/filament/include/gltfio/materials/uberarchive.h @@ -5,9 +5,10 @@ extern "C" { extern const uint8_t UBERARCHIVE_PACKAGE[]; - extern int UBERARCHIVE_DEFAULT_OFFSET; - extern int UBERARCHIVE_DEFAULT_SIZE; } + +#define UBERARCHIVE_DEFAULT_OFFSET 0 +#define UBERARCHIVE_DEFAULT_SIZE 1080249 #define UBERARCHIVE_DEFAULT_DATA (UBERARCHIVE_PACKAGE + UBERARCHIVE_DEFAULT_OFFSET) #endif diff --git a/package/android/libs/filament/include/ibl/CubemapIBL.h b/package/android/libs/filament/include/ibl/CubemapIBL.h index 925e27c0..ae8fcb08 100644 --- a/package/android/libs/filament/include/ibl/CubemapIBL.h +++ b/package/android/libs/filament/include/ibl/CubemapIBL.h @@ -54,7 +54,7 @@ class UTILS_PUBLIC CubemapIBL { * @param updater a callback for the caller to track progress */ static void roughnessFilter( - utils::JobSystem& js, Cubemap& dst, const utils::Slice& levels, + utils::JobSystem& js, Cubemap& dst, utils::Slice levels, float linearRoughness, size_t maxNumSamples, math::float3 mirror, bool prefilter, Progress updater = nullptr, void* userdata = nullptr); diff --git a/package/android/libs/filament/include/math/TQuatHelpers.h b/package/android/libs/filament/include/math/TQuatHelpers.h index 0439b971..7ff641b0 100644 --- a/package/android/libs/filament/include/math/TQuatHelpers.h +++ b/package/android/libs/filament/include/math/TQuatHelpers.h @@ -259,10 +259,11 @@ class TQuatFunctions { return normalize(lerp(d < 0 ? -p : p, q, t)); } const T npq = std::sqrt(dot(p, p) * dot(q, q)); // ||p|| * ||q|| - const T a = std::acos(filament::math::clamp(absd / npq, T(-1), T(1))); + const T cos_a = math::clamp(absd / npq, T(-1), T(1)); + const T a = std::acos(cos_a); const T a0 = a * (1 - t); const T a1 = a * t; - const T sina = sin(a); + const T sina = std::sqrt(T(1) - cos_a * cos_a); if (sina < value_eps) { return normalize(lerp(p, q, t)); } diff --git a/package/android/libs/filament/include/math/compiler.h b/package/android/libs/filament/include/math/compiler.h index 634e2077..a7d85168 100644 --- a/package/android/libs/filament/include/math/compiler.h +++ b/package/android/libs/filament/include/math/compiler.h @@ -122,6 +122,11 @@ struct is_arithmetic : std::integral_constant::value || std::is_floating_point::value> { }; +template +struct is_floating_point : std::integral_constant::value> { +}; + } // filament::math #endif // TNT_MATH_COMPILER_H diff --git a/package/android/libs/filament/include/math/fast.h b/package/android/libs/filament/include/math/fast.h index 85b990d2..9988cfd2 100644 --- a/package/android/libs/filament/include/math/fast.h +++ b/package/android/libs/filament/include/math/fast.h @@ -50,7 +50,7 @@ constexpr T MATH_PURE cos(T x) noexcept { // x between -pi and pi template::value>> constexpr T MATH_PURE sin(T x) noexcept { - return filament::math::fast::cos(x - T(F_PI_2)); + return fast::cos(x - T(F_PI_2)); } constexpr inline float MATH_PURE ilog2(float x) noexcept { diff --git a/package/android/libs/filament/include/math/half.h b/package/android/libs/filament/include/math/half.h index d792e1bf..4578708b 100644 --- a/package/android/libs/filament/include/math/half.h +++ b/package/android/libs/filament/include/math/half.h @@ -164,20 +164,20 @@ inline constexpr half operator""_h(long double v) { return half( static_cast(v) ); } -template<> struct is_arithmetic : public std::true_type {}; +template<> struct is_arithmetic : public std::true_type {}; + +template<> struct is_floating_point : public std::true_type {}; } // namespace math } // namespace filament namespace std { -template<> struct is_floating_point : public std::true_type {}; - -// note: this shouldn't be needed (is_floating_point<> is enough) but some version of msvc need it -// This stopped working with MSVC 2019 16.4, so we specialize our own version of is_arithmetic in +// Remove the standard template specializations for filament::math::half +// Clang 20 explicitly blocks customizing standard type traits +// Instead, use the traits defined in the math:: namespace +// This avoids compatibility issues with Clang 20 and MSVC 2019 16.4+ // the math::filament namespace (see above). -template<> struct is_arithmetic : public std::true_type {}; - template<> class numeric_limits { public: diff --git a/package/android/libs/filament/include/math/mat2.h b/package/android/libs/filament/include/math/mat2.h index dba9ca47..d12aa532 100644 --- a/package/android/libs/filament/include/math/mat2.h +++ b/package/android/libs/filament/include/math/mat2.h @@ -225,7 +225,7 @@ class MATH_EMPTY_BASES TMat22 : * Rotate by radians in the 2D plane */ static TMat22 rotate(T radian) noexcept { - TMat22 r(TMat22::NO_INIT); + TMat22 r(NO_INIT); T c = std::cos(radian); T s = std::sin(radian); r[0][0] = c; diff --git a/package/android/libs/filament/include/math/mat3.h b/package/android/libs/filament/include/math/mat3.h index 035865fe..57ad7ec3 100644 --- a/package/android/libs/filament/include/math/mat3.h +++ b/package/android/libs/filament/include/math/mat3.h @@ -256,7 +256,7 @@ class MATH_EMPTY_BASES TMat33 : */ friend inline constexpr TMat33 orthogonalize(const TMat33& m) noexcept { - TMat33 ret(TMat33::NO_INIT); + TMat33 ret(NO_INIT); ret[0] = normalize(m[0]); ret[2] = normalize(cross(ret[0], m[1])); ret[1] = normalize(cross(ret[2], ret[0])); @@ -479,7 +479,7 @@ constexpr TQuaternion TMat33::packTangentFrame(const TMat33& m, size_t template constexpr details::TMat33 prescaleForNormals(const details::TMat33& m) noexcept { return m * details::TMat33( - 1.0 / std::sqrt(max(float3{length2(m[0]), length2(m[1]), length2(m[2])}))); + T(1.0) / std::sqrt(max(float3{length2(m[0]), length2(m[1]), length2(m[2])}))); } // ---------------------------------------------------------------------------------------- diff --git a/package/android/libs/filament/include/math/mat4.h b/package/android/libs/filament/include/math/mat4.h index 57058539..7eb50e35 100644 --- a/package/android/libs/filament/include/math/mat4.h +++ b/package/android/libs/filament/include/math/mat4.h @@ -498,10 +498,10 @@ constexpr TMat44 TMat44::frustum(T left, T right, T bottom, T top, T near, } template -TMat44 TMat44::perspective(T fov, T aspect, T near, T far, TMat44::Fov direction) noexcept { +TMat44 TMat44::perspective(T fov, T aspect, T near, T far, Fov direction) noexcept { T h, w; - if (direction == TMat44::Fov::VERTICAL) { + if (direction == Fov::VERTICAL) { h = std::tan(fov * F_PI / 360.0f) * near; w = h * aspect; } else { diff --git a/package/android/libs/filament/include/private/backend/VirtualMachineEnv.h b/package/android/libs/filament/include/private/backend/VirtualMachineEnv.h new file mode 100644 index 00000000..c4352391 --- /dev/null +++ b/package/android/libs/filament/include/private/backend/VirtualMachineEnv.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H +#define TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H + +#include +#include + +#include + +namespace filament { + +class VirtualMachineEnv { +public: + // must be called before VirtualMachineEnv::get() from a thread that is attached to the JavaVM + static jint JNI_OnLoad(JavaVM* vm); + + // must be called on backend thread + static VirtualMachineEnv& get() noexcept; + + // can be called from any thread that already has a JniEnv + static JNIEnv* getThreadEnvironment(); + + // must be called from the backend thread + JNIEnv* getEnvironment() noexcept { + JNIEnv* env = mJniEnv; + if (UTILS_UNLIKELY(!env)) { + return getEnvironmentSlow(); + } + return env; + } + + static void handleException(JNIEnv* env) noexcept; + +private: + explicit VirtualMachineEnv(JavaVM* vm) noexcept; + ~VirtualMachineEnv() noexcept; + JNIEnv* getEnvironmentSlow(); + + static utils::Mutex sLock; + static JavaVM* sVirtualMachine; + static JavaVM* getVirtualMachine(); + + JNIEnv* mJniEnv = nullptr; + JavaVM* mVirtualMachine = nullptr; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H diff --git a/package/android/libs/filament/include/uberz/WritableArchive.h b/package/android/libs/filament/include/uberz/WritableArchive.h index e511d95f..b383b70c 100644 --- a/package/android/libs/filament/include/uberz/WritableArchive.h +++ b/package/android/libs/filament/include/uberz/WritableArchive.h @@ -56,7 +56,7 @@ class WritableArchive { utils::FixedCapacityVector package; Shading shadingModel; BlendingMode blendingMode; - tsl::robin_map flags; + tsl::robin_map flags; }; utils::FixedCapacityVector mMaterials; diff --git a/package/android/libs/filament/include/utils/Allocator.h b/package/android/libs/filament/include/utils/Allocator.h index 7b9978f5..d74b8cc8 100644 --- a/package/android/libs/filament/include/utils/Allocator.h +++ b/package/android/libs/filament/include/utils/Allocator.h @@ -18,7 +18,6 @@ #define TNT_UTILS_ALLOCATOR_H #include -#include #include #include @@ -36,22 +35,22 @@ namespace utils { namespace pointermath { -template -static inline P* add(P* a, T b) noexcept { - return (P*)(uintptr_t(a) + uintptr_t(b)); +template +static P* add(P* a, T b) noexcept { + return (P*) (uintptr_t(a) + uintptr_t(b)); } -template -static inline P* align(P* p, size_t alignment) noexcept { +template +static P* align(P* p, size_t alignment) noexcept { // alignment must be a power-of-two - assert_invariant(alignment && !(alignment & alignment-1)); - return (P*)((uintptr_t(p) + alignment - 1) & ~(alignment - 1)); + assert(alignment && !(alignment & alignment-1)); + return (P*) ((uintptr_t(p) + alignment - 1) & ~(alignment - 1)); } -template -static inline P* align(P* p, size_t alignment, size_t offset) noexcept { +template +static P* align(P* p, size_t alignment, size_t offset) noexcept { P* const r = align(add(p, offset), alignment); - assert_invariant(r >= add(p, offset)); + assert(r >= add(p, offset)); return r; } @@ -102,7 +101,7 @@ class LinearAllocator { // free memory back to the specified point void rewind(void* p) UTILS_RESTRICT noexcept { - assert_invariant(p >= mBegin && p < end()); + assert(p >= mBegin && p < end()); set_current(p); } @@ -194,7 +193,7 @@ class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocat } ~LinearAllocatorWithFallback() noexcept { - LinearAllocatorWithFallback::reset(); + reset(); } void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)); @@ -204,7 +203,7 @@ class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocat } void rewind(void* p) noexcept { - if (p >= LinearAllocator::base() && p < LinearAllocator::end()) { + if (p >= base() && p < end()) { LinearAllocator::rewind(p); } } @@ -214,7 +213,7 @@ class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocat void free(void*, size_t) noexcept { } bool isHeapAllocation(void* p) const noexcept { - return p < LinearAllocator::base() || p >= LinearAllocator::end(); + return p < base() || p >= end(); } }; @@ -233,15 +232,16 @@ class FreeList { Node* const head = mHead; mHead = head ? head->next : nullptr; // this could indicate a use after free - assert_invariant(!mHead || mHead >= mBegin && mHead < mEnd); + assert(!mHead || mHead >= mBegin && mHead < mEnd); return head; } void push(void* p) noexcept { - assert_invariant(p); - assert_invariant(p >= mBegin && p < mEnd); - // TODO: assert this is one of our pointer (i.e.: it's address match one of ours) - Node* const head = static_cast(p); + assert(p); + assert(p >= mBegin && p < mEnd); + // we use placement-new to properly manage the lifetime + // this is noop already under O1 + Node* const head = new (p) Node; head->next = mHead; mHead = head; } @@ -278,41 +278,50 @@ class AtomicFreeList { void* pop() noexcept { Node* const pStorage = mStorage; - HeadPtr currentHead = mHead.load(); + HeadPtr currentHead = mHead.load(std::memory_order_relaxed); while (currentHead.offset >= 0) { // The value of "pNext" we load here might already contain application data if another // thread raced ahead of us. But in that case, the computed "newHead" will be discarded - // since compare_exchange_weak fails. Then this thread will loop with the updated + // since compare_exchange_weak() fails. Then this thread will loop with the updated // value of currentHead, and try again. - Node* const pNext = pStorage[currentHead.offset].next.load(std::memory_order_relaxed); + // TSAN complains if we don't use a local variable here. + Node const node = pStorage[currentHead.offset]; + Node const* const pNext = node.next; const HeadPtr newHead{ pNext ? int32_t(pNext - pStorage) : -1, currentHead.tag + 1 }; - // In the rare case that the other thread that raced ahead of us already returned the - // same mHead we just loaded, but it now has a different "next" value, the tag field will not - // match, and compare_exchange_weak will fail and prevent that particular race condition. - if (mHead.compare_exchange_weak(currentHead, newHead)) { + // In the rare case that the other thread that raced ahead of us already returned the + // same mHead we just loaded, but it now has a different "next" value, the tag field + // will not match, and compare_exchange_weak() will fail and prevent that particular + // race condition. + // acquire: no read/write can be reordered before this + if (mHead.compare_exchange_weak(currentHead, newHead, + std::memory_order_acquire, std::memory_order_relaxed)) { // This assert needs to occur after we have validated that there was no race condition // Otherwise, next might already contain application data, if another thread // raced ahead of us after we loaded mHead, but before we loaded mHead->next. - assert_invariant(!pNext || pNext >= pStorage); + assert(!pNext || pNext >= pStorage); break; } } void* p = (currentHead.offset >= 0) ? (pStorage + currentHead.offset) : nullptr; - assert_invariant(!p || p >= pStorage); + assert(!p || p >= pStorage); return p; } void push(void* p) noexcept { Node* const storage = mStorage; - assert_invariant(p && p >= storage); - Node* const node = static_cast(p); - HeadPtr currentHead = mHead.load(); + assert(p && p >= storage); + // we use placement-new to properly manage the lifetime + // this is noop already under O1 + Node* const node = new (p) Node; + HeadPtr currentHead = mHead.load(std::memory_order_relaxed); HeadPtr newHead = { int32_t(node - storage), currentHead.tag + 1 }; do { newHead.tag = currentHead.tag + 1; - Node* const n = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr; - node->next.store(n, std::memory_order_relaxed); - } while(!mHead.compare_exchange_weak(currentHead, newHead)); + Node* const pNext = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr; + node->next = pNext; // could be a race with pop, corrected by CAS + } while(!mHead.compare_exchange_weak(currentHead, newHead, + std::memory_order_release, std::memory_order_relaxed)); + // release: no read/write can be reordered after this } void* getFirst() noexcept { @@ -320,10 +329,7 @@ class AtomicFreeList { } struct Node { - // This should be a regular (non-atomic) pointer, but this causes TSAN to complain - // about a data-race that exists but is benin. We always use this atomic<> in - // relaxed mode. - // The data race TSAN complains about is when a pop() is interrupted by a + // There is a benign data race when a pop() is interrupted by a // pop() + push() just after mHead->next is read -- it appears as though it is written // without synchronization (by the push), however in that case, the pop's CAS will fail // and things will auto-correct. @@ -346,7 +352,7 @@ class AtomicFreeList { // | // CAS, tag++ // - std::atomic next; + Node* next = nullptr; }; private: @@ -378,9 +384,9 @@ class PoolAllocator { // our allocator concept void* alloc(size_t size = ELEMENT_SIZE, size_t alignment = ALIGNMENT, size_t offset = OFFSET) noexcept { - assert_invariant(size <= ELEMENT_SIZE); - assert_invariant(alignment <= ALIGNMENT); - assert_invariant(offset == OFFSET); + assert(size <= ELEMENT_SIZE); + assert(alignment <= ALIGNMENT); + assert(offset == OFFSET); return mFreeList.pop(); } @@ -458,7 +464,7 @@ class PoolAllocatorWithFallback : if (UTILS_UNLIKELY(!p)) { p = HeapAllocator::alloc(size, alignment); } - assert_invariant(p); + assert(p); return p; } @@ -714,13 +720,13 @@ class Arena { // trivially destructible, since free() won't call the destructor and this is allocating // an array. template ::value>::type> + typename = std::enable_if_t>> T* alloc(size_t count, size_t alignment, size_t extra) noexcept { return (T*)alloc(count * sizeof(T), alignment, extra); } template ::value>::type> + typename = std::enable_if_t>> T* alloc(size_t count, size_t alignment = alignof(T)) noexcept { return (T*)alloc(count * sizeof(T), alignment); } @@ -876,7 +882,7 @@ class ArenaScope { template T* make(ARGS&& ... args) noexcept { T* o = nullptr; - if (std::is_trivially_destructible::value) { + if (std::is_trivially_destructible_v) { o = mArena.template make(std::forward(args)...); } else { void* const p = (Finalizer*)mArena.alloc(sizeof(T), ALIGN, sizeof(Finalizer)); @@ -939,7 +945,7 @@ class STLAllocator { TYPE* allocate(std::size_t n) { auto p = static_cast(mArena.alloc(n * sizeof(TYPE), alignof(TYPE))); - assert_invariant(p); + assert(p); return p; } diff --git a/package/android/libs/filament/include/utils/BitmaskEnum.h b/package/android/libs/filament/include/utils/BitmaskEnum.h index 17f94d21..ae412204 100644 --- a/package/android/libs/filament/include/utils/BitmaskEnum.h +++ b/package/android/libs/filament/include/utils/BitmaskEnum.h @@ -41,32 +41,32 @@ size_t count(); // ------------------------------------------------------------------------------------------------ template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr int operator+(Enum value) noexcept { return int(value); } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator==(Enum lhs, size_t rhs) noexcept { using underlying_t = std::underlying_type_t; return underlying_t(lhs) == rhs; } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator==(size_t lhs, Enum rhs) noexcept { return rhs == lhs; } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator!=(Enum lhs, size_t rhs) noexcept { return !(rhs == lhs); } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator!=(size_t lhs, Enum rhs) noexcept { return rhs != lhs; } @@ -74,66 +74,66 @@ inline constexpr bool operator!=(size_t lhs, Enum rhs) noexcept { // ------------------------------------------------------------------------------------------------ template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr bool operator!(Enum rhs) noexcept { using underlying = std::underlying_type_t; return underlying(rhs) == 0; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator~(Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(~underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator|(Enum lhs, Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(underlying(lhs) | underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator&(Enum lhs, Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(underlying(lhs) & underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator^(Enum lhs, Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(underlying(lhs) ^ underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator|=(Enum& lhs, Enum rhs) noexcept { return lhs = lhs | rhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator&=(Enum& lhs, Enum rhs) noexcept { return lhs = lhs & rhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator^=(Enum& lhs, Enum rhs) noexcept { return lhs = lhs ^ rhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr bool none(Enum lhs) noexcept { return !lhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr bool any(Enum lhs) noexcept { return !none(lhs); } diff --git a/package/android/libs/filament/include/utils/CString.h b/package/android/libs/filament/include/utils/CString.h index d5b76951..935845f2 100644 --- a/package/android/libs/filament/include/utils/CString.h +++ b/package/android/libs/filament/include/utils/CString.h @@ -20,6 +20,11 @@ // NOTE: this header should not include STL headers #include +#include + +#include +#include +#include #include #include @@ -28,27 +33,22 @@ #include namespace utils { - -//! \privatesection -struct hashCStrings { - typedef const char* argument_type; - typedef size_t result_type; - result_type operator()(argument_type cstr) const noexcept { - size_t hash = 5381; - while (int const c = *cstr++) { - hash = (hash * 33u) ^ size_t(c); - } - return hash; - } -}; +namespace io { +class ostream; +} template using StringLiteral = const char[N]; - // ------------------------------------------------------------------------------------------------ class UTILS_PUBLIC CString { + static constexpr bool TRACK_AND_LOG_ALLOCATIONS = false; + + template + static constexpr bool is_char_pointer_v = + std::is_pointer_v && std::is_same_v>>; + public: using value_type = char; using size_type = uint32_t; @@ -60,7 +60,9 @@ class UTILS_PUBLIC CString { using iterator = value_type*; using const_iterator = const value_type*; - CString() noexcept {} // NOLINT(modernize-use-equals-default), Ubuntu compiler bug + CString() noexcept { + track(true); + } // Allocates memory and appends a null. This constructor can be used to hold arbitrary data // inside the string (i.e. it can contain nulls or non-ASCII encodings). @@ -71,22 +73,45 @@ class UTILS_PUBLIC CString { // inside the string. explicit CString(size_t length); + // Conversion from std::string_view + explicit CString(const std::string_view& str) + : CString(str.data(), str.size()) { + } + // Allocates memory and copies traditional C string content. Unlike the above constructor, this // does not allow embedded nulls. This is explicit because this operation is costly. - explicit CString(const char* cstr); + // This is a template to ensure it's not preferred over the string literal constructor below. + template>> + explicit CString(T cstr) : CString(cstr, cstr ? strlen(cstr) : 0) { + track(true); + } + // The string can't have NULs in it. template CString(StringLiteral const& other) noexcept // NOLINT(google-explicit-constructor) : CString(other, N - 1) { + track(true); + } + + // This constructor can be used if the string has NULs in it. + template + CString(StringLiteral const& other, size_t const length) noexcept + : CString(other, length) { + track(true); + } + + CString(StaticString const& other) noexcept // NOLINT(*-explicit-constructor) + : CString(other.c_str(), other.length()) { + track(true); } CString(const CString& rhs); CString(CString&& rhs) noexcept { + track(true); this->swap(rhs); } - CString& operator=(const CString& rhs); CString& operator=(CString&& rhs) noexcept { @@ -94,11 +119,7 @@ class UTILS_PUBLIC CString { return *this; } - ~CString() noexcept { - if (mData) { - free(mData - 1); - } - } + ~CString() noexcept; void swap(CString& other) noexcept { // don't use std::swap(), we don't want an STL dependency in this file @@ -107,8 +128,8 @@ class UTILS_PUBLIC CString { other.mCStr = temp; } - const_pointer c_str() const noexcept { return mCStr; } pointer c_str() noexcept { return mCStr; } + const_pointer c_str() const noexcept { return const_cast(this)->c_str(); } const_pointer c_str_safe() const noexcept { return mData ? c_str() : ""; } const_pointer data() const noexcept { return c_str(); } pointer data() noexcept { return c_str(); } @@ -116,32 +137,169 @@ class UTILS_PUBLIC CString { size_type length() const noexcept { return size(); } bool empty() const noexcept { return size() == 0; } - iterator begin() noexcept { return mCStr; } + iterator begin() noexcept { return c_str(); } iterator end() noexcept { return begin() + length(); } const_iterator begin() const noexcept { return data(); } const_iterator end() const noexcept { return begin() + length(); } const_iterator cbegin() const noexcept { return begin(); } const_iterator cend() const noexcept { return end(); } - CString& replace(size_type pos, size_type len, const CString& str) noexcept; - CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); } + // replace + template + CString& replace(size_type const pos, size_type const len, const StringLiteral& str) & noexcept { + return replace(pos, len, str, N - 1); + } + + CString& replace(size_type const pos, size_type const len, const CString& str) & noexcept { + return replace(pos, len, str.c_str_safe(), str.size()); + } + + template >> + CString& replace(size_type pos, size_type len, T str) & noexcept { + if (str) { + return replace(pos, len, str, strlen(str)); + } + return replace(pos, len, "", 0); + } + + template + CString&& replace(size_type pos, size_type len, const StringLiteral& str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + CString&& replace(size_type const pos, size_type const len, const CString& str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + template >> + CString&& replace(size_type pos, size_type len, T str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + + // insert + CString& insert(size_type const pos, char const c) & noexcept { + const char s[1] = { c }; + return replace(pos, 0, s, 1); + } + + template + CString& insert(size_type const pos, const StringLiteral& str) & noexcept { + return replace(pos, 0, str, N - 1); + } + + CString& insert(size_type const pos, const CString& str) & noexcept { + return replace(pos, 0, str.c_str_safe(), str.size()); + } + + template >> + CString& insert(size_type pos, T str) & noexcept { + if (str) { + return replace(pos, 0, str, strlen(str)); + } + return *this; + } + + CString&& insert(size_type const pos, char const c) && noexcept { + this->insert(pos, c); + return std::move(*this); + } + + template + CString&& insert(size_type pos, const StringLiteral& str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + CString&& insert(size_type const pos, const CString& str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + template >> + CString&& insert(size_type pos, T str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + + // append + CString& append(char const c) & noexcept { + return insert(length(), c); + } + + template + CString& append(const StringLiteral& str) & noexcept { + return insert(length(), str); + } + + CString& append(const CString& str) & noexcept { + return insert(length(), str); + } + + template>> + CString& append(T str) & noexcept { + return insert(length(), str); + } + + CString&& append(char const c) && noexcept { + this->append(c); + return std::move(*this); + } + + template + CString&& append(const StringLiteral& str) && noexcept { + this->append(str); + return std::move(*this); + } + + CString&& append(const CString& str) && noexcept { + this->append(str); + return std::move(*this); + } + + template>> + CString&& append(T str) && noexcept { + this->append(str); + return std::move(*this); + } + + // operator+= + CString& operator+=(char const c) & noexcept { + return append(c); + } + + CString& operator+=(const CString& str) & noexcept { + return append(str); + } + template + CString& operator+=(const StringLiteral& str) & noexcept { + return append(str); + } + template >> + CString& operator+=(T str) & noexcept { + return append(str); + } - const_reference operator[](size_type pos) const noexcept { + const_reference operator[](size_type const pos) const noexcept { assert(pos < size()); return begin()[pos]; } - reference operator[](size_type pos) noexcept { + reference operator[](size_type const pos) noexcept { assert(pos < size()); return begin()[pos]; } - const_reference at(size_type pos) const noexcept { + const_reference at(size_type const pos) const noexcept { assert(pos < size()); return begin()[pos]; } - reference at(size_type pos) noexcept { + reference at(size_type const pos) noexcept { assert(pos < size()); return begin()[pos]; } @@ -167,20 +325,30 @@ class UTILS_PUBLIC CString { } // placement new declared as "throw" to avoid the compiler's null-check - inline void* operator new(size_t, void* ptr) { + void* operator new(size_t, void* ptr) { assert(ptr); return ptr; } - struct Hasher : private hashCStrings { - typedef CString argument_type; - typedef size_t result_type; - result_type operator()(const argument_type& s) const noexcept { - return hashCStrings::operator()(s.c_str()); - } - }; + // conversion to std::string_view + operator std::string_view() const noexcept { + return std::string_view{data(), size()}; + } private: + static void do_tracking(bool ctor); + static void track(bool ctor) { + if constexpr (TRACK_AND_LOG_ALLOCATIONS) { + do_tracking(ctor); + } + } + + CString& replace(size_type pos, size_type len, char const* str, size_t l) & noexcept; + +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const CString& rhs); +#endif + struct Data { size_type length; }; @@ -191,17 +359,16 @@ class UTILS_PUBLIC CString { Data* mData; // Data is stored at mData[-1] }; + int compare(const std::string_view& rhs) const noexcept { + return std::string_view{data(), size()}.compare(rhs); + } + int compare(const CString& rhs) const noexcept { - size_type const lhs_size = size(); - size_type const rhs_size = rhs.size(); - if (lhs_size < rhs_size) return -1; - if (lhs_size > rhs_size) return 1; - return strncmp(data(), rhs.data(), size()); + return compare(std::string_view{rhs.data(), rhs.size()}); } friend bool operator==(CString const& lhs, CString const& rhs) noexcept { - return (lhs.data() == rhs.data()) || - ((lhs.size() == rhs.size()) && !strncmp(lhs.data(), rhs.data(), lhs.size())); + return lhs.compare(rhs) == 0; } friend bool operator!=(CString const& lhs, CString const& rhs) noexcept { return !(lhs == rhs); @@ -218,9 +385,125 @@ class UTILS_PUBLIC CString { friend bool operator<=(CString const& lhs, CString const& rhs) noexcept { return !(lhs > rhs); } + + friend bool operator==(CString const& lhs, std::string_view const& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + friend bool operator==(std::string_view const& lhs, CString const& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(CString const& lhs, std::string_view const& rhs) noexcept { + return !(lhs == rhs); + } + friend bool operator!=(std::string_view const& lhs, CString const& rhs) noexcept { + return !(lhs == rhs); + } + friend bool operator<(CString const& lhs, std::string_view const& rhs) noexcept { + return lhs.compare(rhs) < 0; + } + friend bool operator<(std::string_view const& lhs, CString const& rhs) noexcept { + return lhs.compare(rhs) < 0; + } + friend bool operator>(CString const& lhs, std::string_view const& rhs) noexcept { + return lhs.compare(rhs) > 0; + } + friend bool operator>(std::string_view const& lhs, CString const& rhs) noexcept { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(CString const& lhs, std::string_view const& rhs) noexcept { + return !(lhs < rhs); + } + friend bool operator>=(std::string_view const& lhs, CString const& rhs) noexcept { + return !(lhs < rhs); + } + friend bool operator<=(CString const& lhs, std::string_view const& rhs) noexcept { + return !(lhs > rhs); + } + friend bool operator<=(std::string_view const& lhs, CString const& rhs) noexcept { + return !(lhs > rhs); + } }; -// implement this for your type for automatic conversion to CString. Failing to do so leads +// operator+ +inline CString operator+(CString lhs, const CString& rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(CString lhs, const char* rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(const char* lhs, CString rhs) { + rhs.insert(0, lhs); + return rhs; +} + +inline CString operator+(CString lhs, char const rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(char const lhs, CString rhs) { + rhs.insert(0, lhs); + return rhs; +} + +// CString vs StringLiteral +template + bool operator==(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} == std::string_view{rhs, N - 1}; +} +template + bool operator!=(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} != std::string_view{rhs, N - 1}; +} +template + bool operator<(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} < std::string_view{rhs, N - 1}; +} +template + bool operator>(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} > std::string_view{rhs, N - 1}; +} +template + bool operator<=(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} <= std::string_view{rhs, N - 1}; +} +template + bool operator>=(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} >= std::string_view{rhs, N - 1}; +} + +// StringLiteral vs CString +template + bool operator==(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} == std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator!=(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} != std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator<(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} < std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator>(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} > std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator<=(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} <= std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator>=(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} >= std::string_view{rhs.data(), rhs.size()}; +} + + +// Implement this for your type for automatic conversion to CString. Failing to do so leads // to a compile-time failure. template CString to_string(T value) noexcept; @@ -244,9 +527,47 @@ class UTILS_PUBLIC FixedSizeString { pointer c_str() noexcept { return mData; } private: - value_type mData[N] = {0}; + value_type mData[N] = {}; }; } // namespace utils +// heterogeneous lookup support for associative containers +namespace std { + template <> + struct hash { + using is_transparent = void; // Enable heterogeneous lookup + + size_t operator()(const utils::CString& k) const noexcept { + return compute_hash(std::string_view(k)); + } + + template && + !std::is_same_v, utils::CString>>> + size_t operator()(const T& k) const noexcept { + return compute_hash(std::string_view(k)); + } + + private: + size_t compute_hash(std::string_view k) const noexcept { + size_t hash = 5381; + for (char const c : k) { + hash = (hash * 33u) ^ size_t(c); + } + return hash; + } + }; + + template<> + struct equal_to { + using is_transparent = void; // Enable heterogeneous lookup + + template + bool operator()(const T& lhs, const U& rhs) const { + return lhs == rhs; + } + }; +} + #endif // TNT_UTILS_CSTRING_H diff --git a/package/android/libs/filament/include/utils/CallStack.h b/package/android/libs/filament/include/utils/CallStack.h index 33ac0b50..f8366cfa 100644 --- a/package/android/libs/filament/include/utils/CallStack.h +++ b/package/android/libs/filament/include/utils/CallStack.h @@ -23,9 +23,11 @@ #include #include -#include namespace utils { +namespace io { +class ostream; +} /** * CallStack captures the current's thread call stack. @@ -68,10 +70,10 @@ class CallStack { intptr_t operator [](size_t index) const; /** Demangles a C++ type name */ - static utils::CString demangleTypeName(const char* mangled); + static CString demangleTypeName(const char* mangled); template - static utils::CString typeName() { + static CString typeName() { #if UTILS_HAS_RTTI return demangleTypeName(typeid(T).name()); #else @@ -84,34 +86,34 @@ class CallStack { * This will print, when possible, the demangled names of functions corresponding to the * program-counter recorded. */ - friend utils::io::ostream& operator <<(utils::io::ostream& stream, const CallStack& callstack); + friend io::ostream& operator <<(io::ostream& stream, const CallStack& callstack); bool operator <(const CallStack& rhs) const; - inline bool operator >(const CallStack& rhs) const { + bool operator >(const CallStack& rhs) const { return rhs < *this; } - inline bool operator !=(const CallStack& rhs) const { + bool operator !=(const CallStack& rhs) const { return *this < rhs || rhs < *this; } - inline bool operator >=(const CallStack& rhs) const { + bool operator >=(const CallStack& rhs) const { return !operator <(rhs); } - inline bool operator <=(const CallStack& rhs) const { + bool operator <=(const CallStack& rhs) const { return !operator >(rhs); } - inline bool operator ==(const CallStack& rhs) const { + bool operator ==(const CallStack& rhs) const { return !operator !=(rhs); } private: void update_gcc(size_t ignore) noexcept; - static utils::CString demangle(const char* mangled); + static CString demangle(const char* mangled); static constexpr size_t NUM_FRAMES = 20; diff --git a/package/android/libs/filament/include/utils/FixedCapacityVector.h b/package/android/libs/filament/include/utils/FixedCapacityVector.h index 1221e7cc..fd920876 100644 --- a/package/android/libs/filament/include/utils/FixedCapacityVector.h +++ b/package/android/libs/filament/include/utils/FixedCapacityVector.h @@ -17,10 +17,11 @@ #ifndef TNT_UTILS_FIXEDCAPACITYVECTOR_H #define TNT_UTILS_FIXEDCAPACITYVECTOR_H +#include #include #include -#include +#include #include #include #include @@ -40,6 +41,11 @@ namespace utils { +class UTILS_PUBLIC FixedCapacityVectorBase { +protected: + UTILS_NORETURN static void capacityCheckFailed(size_t capacity, size_t size); +}; + /** * FixedCapacityVector is (almost) a drop-in replacement for std::vector<> except it has a * fixed capacity decided at runtime. The vector storage is never reallocated unless reserve() @@ -56,7 +62,7 @@ namespace utils { * the optional value argument, e.g. FixedCapacityVector(4, 0) or foo.resize(4, 0). */ template, bool CapacityCheck = true> -class UTILS_PUBLIC FixedCapacityVector { +class UTILS_PUBLIC FixedCapacityVector : protected FixedCapacityVectorBase { public: using allocator_type = A; using value_type = T; @@ -84,7 +90,7 @@ class UTILS_PUBLIC FixedCapacityVector { FixedCapacityVector() = default; explicit FixedCapacityVector(const allocator_type& allocator) noexcept - : mCapacityAllocator({}, allocator) { + : mCapacityAllocator(0, allocator) { } explicit FixedCapacityVector(size_type size, const allocator_type& allocator = allocator_type()) @@ -122,6 +128,14 @@ class UTILS_PUBLIC FixedCapacityVector { this->swap(rhs); } + FixedCapacityVector(utils::Slice rhs, + const allocator_type& alloc = allocator_type()) + : mSize(rhs.size()), + mCapacityAllocator(mSize, alloc) { + mData = this->allocator().allocate(this->capacity()); + std::uninitialized_copy(rhs.cbegin(), rhs.cend(), begin()); + } + ~FixedCapacityVector() noexcept { destroy(begin(), end()); allocator().deallocate(data(), capacity()); @@ -140,6 +154,29 @@ class UTILS_PUBLIC FixedCapacityVector { return *this; } + bool operator==(const FixedCapacityVector& rhs) const noexcept { + if (this == &rhs) { + return true; + } + if (size() != rhs.size()) { + return false; + } + return std::equal(begin(), end(), rhs.begin()); + } + + Slice as_slice() noexcept { + return { begin(), end() }; + } + + Slice as_slice() const noexcept { + return { cbegin(), cend() }; + } + + template> + inline size_t hash() const noexcept { + return as_slice().template hash(); + } + allocator_type get_allocator() const noexcept { return mCapacityAllocator.second(); } @@ -266,7 +303,7 @@ class UTILS_PUBLIC FixedCapacityVector { mSize = 0; } - void resize(size_type count) { + void resize(size_type const count) { assertCapacityForSize(count); if constexpr(std::is_trivially_constructible_v && std::is_trivially_destructible_v) { @@ -277,12 +314,12 @@ class UTILS_PUBLIC FixedCapacityVector { } } - void resize(size_type count, const_reference v) { + void resize(size_type const count, const_reference v) { assertCapacityForSize(count); resize_non_trivial(count, v); } - void swap(FixedCapacityVector& other) { + void swap(FixedCapacityVector& other) noexcept { using std::swap; swap(mData, other.mData); swap(mSize, other.mSize); @@ -326,16 +363,16 @@ class UTILS_PUBLIC FixedCapacityVector { return mCapacityAllocator.second(); } - iterator assertCapacityForSize(size_type s) { + iterator assertCapacityForSize(size_type const s) { if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) { - FILAMENT_CHECK_PRECONDITION(capacity() >= s) - << "capacity exceeded: requested size " << (unsigned long)s - << "u, available capacity " << (unsigned long)capacity() << "u."; + if (UTILS_VERY_UNLIKELY(capacity() < s)) { + capacityCheckFailed(capacity(), s); + } } return end(); } - inline void construct(iterator first, iterator last) noexcept { + void construct(iterator const first, iterator const last) noexcept { // we check for triviality here so that the implementation could be non-inline if constexpr(!std::is_trivially_constructible_v) { construct_non_trivial(first, last); @@ -358,7 +395,7 @@ class UTILS_PUBLIC FixedCapacityVector { } - inline void destroy(iterator first, iterator last) noexcept { + void destroy(iterator const first, iterator const last) noexcept { // we check for triviality here so that the implementation could be non-inline if constexpr(!std::is_trivially_destructible_v) { destroy_non_trivial(first, last); @@ -419,7 +456,7 @@ class UTILS_PUBLIC FixedCapacityVector { explicit SizeTypeWrapper(TYPE value) noexcept : value(value) { } SizeTypeWrapper& operator=(TYPE rhs) noexcept { value = rhs; return *this; } SizeTypeWrapper& operator=(SizeTypeWrapper& rhs) noexcept = delete; - operator TYPE() const noexcept { return value; } + operator TYPE() const noexcept { return value; } // NOLINT(*-explicit-constructor) }; pointer mData{}; @@ -429,4 +466,13 @@ class UTILS_PUBLIC FixedCapacityVector { } // namespace utils +namespace std { +template +struct hash> { + inline size_t operator()(utils::FixedCapacityVector const& lhs) const noexcept { + return lhs.hash(); + } +}; +} // namespace std + #endif // TNT_UTILS_FIXEDCAPACITYVECTOR_H diff --git a/package/android/libs/filament/include/utils/Hash.h b/package/android/libs/filament/include/utils/Hash.h new file mode 100644 index 00000000..f1ff3aff --- /dev/null +++ b/package/android/libs/filament/include/utils/Hash.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_HASH_H +#define TNT_UTILS_HASH_H + +#include // for std::hash +#include +#include +#include + +#include +#include + +namespace utils::hash { + +// The standard CRC-32 polynomial (used in Ethernet, ZIP, PNG, etc.) +static constexpr uint32_t CRC32_POLYNOMIAL = 0xEDB88320; + +// Generates the lookup table for the CRC-32 algorithm. +inline void crc32GenerateTable(std::vector& table) { + if (table.size() != 256) { + table.resize(256); + } + + for (uint32_t i = 0; i < 256; ++i) { + uint32_t crc = i; + for (int j = 0; j < 8; ++j) { + if (crc & 1) { + crc = (crc >> 1) ^ CRC32_POLYNOMIAL; + } else { + crc = crc >> 1; + } + } + table[i] = crc; + } +} + +// Updates the CRC-32 value with a new chunk of data. This function can be called multiple times to +// process a large buffer in smaller, separate parts. +// @param previous_crc The CRC value returned from the previous call to `crc32Update`. +// @param data A pointer to the data buffer for this chunk. +// @param length The length of the data buffer in bytes. +// @return The new CRC value. +inline uint32_t crc32Update(uint32_t previous_crc, const void* data, size_t length, const std::vector& table) { + uint32_t crc = ~previous_crc; + const uint8_t* current = static_cast(data); + for (size_t i = 0; i < length; ++i) { + // The core CRC-32 calculation step. + crc = (crc >> 8) ^ table[(crc ^ current[i]) & 0xFF]; + } + return ~crc; +} + +inline size_t combine(size_t lhs, size_t rhs) noexcept { + std::pair const p{ lhs, rhs }; + return std::hash{}({ (char*)&p, sizeof(p) }); +} + +// Hash function that takes an arbitrary swath of word-aligned data. +inline uint32_t murmur3(const uint32_t* key, size_t wordCount, uint32_t seed) noexcept { + uint32_t h = seed; + size_t i = wordCount; + do { + uint32_t k = *key++; + k *= 0xcc9e2d51u; + k = (k << 15u) | (k >> 17u); + k *= 0x1b873593u; + h ^= k; + h = (h << 13u) | (h >> 19u); + h = (h * 5u) + 0xe6546b64u; + } while (--i); + h ^= wordCount; + h ^= h >> 16u; + h *= 0x85ebca6bu; + h ^= h >> 13u; + h *= 0xc2b2ae35u; + h ^= h >> 16u; + return h; +} + +// The hash yields the same result for a given byte sequence regardless of alignment. +inline uint32_t murmurSlow(const uint8_t* key, size_t byteCount, uint32_t seed) noexcept { + const size_t wordCount = (byteCount + 3) / 4; + const uint8_t* const last = key + byteCount; + + // The remainder is identical to murmur3() except an inner loop safely "reads" an entire word. + uint32_t h = seed; + size_t wc = wordCount; + do { + uint32_t k = 0; + for (int i = 0; i < 4 && key < last; ++i, ++key) { + k >>= 8; + k |= uint32_t(*key) << 24; + } + k *= 0xcc9e2d51u; + k = (k << 15u) | (k >> 17u); + k *= 0x1b873593u; + h ^= k; + h = (h << 13u) | (h >> 19u); + h = (h * 5u) + 0xe6546b64u; + } while (--wc); + h ^= wordCount; + h ^= h >> 16u; + h *= 0x85ebca6bu; + h ^= h >> 13u; + h *= 0xc2b2ae35u; + h ^= h >> 16u; + return h; +} + +template +struct MurmurHashFn { + uint32_t operator()(const T& key) const noexcept { + static_assert(0 == (sizeof(key) & 3u), "Hashing requires a size that is a multiple of 4."); + return murmur3((const uint32_t*) &key, sizeof(key) / 4, 0); + } +}; + +// combines two hashes together +template +inline void combine(size_t& seed, const T& v) noexcept { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u); +} + +// combines two hashes together, faster but less good +template +inline void combine_fast(size_t& seed, const T& v) noexcept { + std::hash hasher; + seed ^= hasher(v) << 1u; +} + +} // namespace utils::hash + +#endif // TNT_UTILS_HASH_H diff --git a/package/android/libs/filament/include/utils/ImmutableCString.h b/package/android/libs/filament/include/utils/ImmutableCString.h new file mode 100644 index 00000000..48fbf430 --- /dev/null +++ b/package/android/libs/filament/include/utils/ImmutableCString.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_IMMUTABLECSTRING_H +#define TNT_UTILS_IMMUTABLECSTRING_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace utils { + + +class UTILS_PUBLIC ImmutableCString { + static constexpr bool TRACK_AND_LOG_ALLOCATIONS = false; + + template + static constexpr bool is_char_pointer_v = + std::is_pointer_v && std::is_same_v>>; + +public: + using value_type = char; + using size_type = uint32_t; + using difference_type = int32_t; + using const_reference = const value_type&; + using const_pointer = const value_type*; + using const_iterator = const value_type*; + + ImmutableCString() noexcept { + track(true, mIsStatic); + } + + // The string can't have NULs in it. + template + ImmutableCString(const char (&str)[N]) noexcept : mData(str), mSize(N - 1) { // NOLINT(*-explicit-constructor) + track(true, mIsStatic); + } + + // This constructor can be used if the string has NULs in it. + template + ImmutableCString(const char (&str)[N], size_t const length) noexcept + : mData(str), mSize(length) { + track(true, mIsStatic); + } + + template>> + explicit ImmutableCString(T cstr) { + if (cstr) { + initializeFrom(cstr, strlen(cstr)); + } + track(true, mIsStatic); + } + + ImmutableCString(const char* cstr, size_t const length) { + initializeFrom(cstr, length); + track(true, mIsStatic); + } + + ImmutableCString(StaticString const& str) // NOLINT(*-explicit-constructor) + : mData(str.data()), mSize(str.size()) { + track(true, mIsStatic); + } + + ImmutableCString(const ImmutableCString& other) { + if (other.mIsStatic) { + mIsStatic = other.mIsStatic; + mSize = other.mSize; + mData = other.mData; + } else { + initializeFrom(other.mData, other.mSize); + } + track(true, mIsStatic); + } + + ImmutableCString(ImmutableCString&& other) noexcept { + track(true, mIsStatic); + this->swap(other); + } + + ImmutableCString& operator=(const ImmutableCString& other); + + ImmutableCString& operator=(ImmutableCString&& other) noexcept; + + ~ImmutableCString() { + track(false, mIsStatic); + if (!mIsStatic) { + free(const_cast(mData)); + } + } + + bool isStatic() const noexcept { return mIsStatic; } + bool isDynamic() const noexcept { return !mIsStatic; } + + const_pointer c_str_safe() const noexcept { return mData; } + const_pointer c_str() const noexcept { return mData; } + const_pointer data() const noexcept { return mData; } + size_type size() const noexcept { return mSize; } + size_type length() const noexcept { return mSize; } + bool empty() const noexcept { return mSize == 0; } + + const_iterator begin() const noexcept { return mData; } + const_iterator end() const noexcept { return mData + mSize; } + const_iterator cbegin() const noexcept { return begin(); } + const_iterator cend() const noexcept { return end(); } + + const_reference operator[](size_type const pos) const noexcept { + assert(pos < mSize); + return mData[pos]; + } + + const_reference at(size_type const pos) const noexcept { + assert(pos < mSize); + return mData[pos]; + } + + const_reference front() const noexcept { + assert(mSize > 0); + return mData[0]; + } + + const_reference back() const noexcept { + assert(mSize > 0); + return mData[mSize - 1]; + } + + void swap(ImmutableCString& other) noexcept { + std::swap(mData, other.mData); + std::swap(mSize, other.mSize); + std::swap(mIsStatic, other.mIsStatic); + } + +private: + static void do_tracking(bool ctor, bool is_static); + static void track(bool ctor, bool is_static) { + if constexpr (TRACK_AND_LOG_ALLOCATIONS) { + do_tracking(ctor, is_static); + } + } + +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const ImmutableCString& rhs); +#endif + + void initializeFrom(const char* cstr, size_t length); + + int compare(const ImmutableCString& rhs) const noexcept { + return std::string_view{ mData, mSize }.compare({ rhs.mData, rhs.mSize }); + } + + char const* mData = ""; + uint32_t mSize = 0; + bool mIsStatic = true; + + friend bool operator==(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) != 0; + } + friend bool operator<(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) < 0; + } + friend bool operator>(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) > 0; + } + friend bool operator<=(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) <= 0; + } + friend bool operator>=(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) >= 0; + } +}; + +static_assert(sizeof(ImmutableCString) <= 16, "ImmutableCString should be 16 bytes or less"); + +} // namespace utils + +#endif //TNT_UTILS_IMMUTABLECSTRING_H diff --git a/package/android/libs/filament/include/utils/InternPool.h b/package/android/libs/filament/include/utils/InternPool.h new file mode 100644 index 00000000..b9ccccbd --- /dev/null +++ b/package/android/libs/filament/include/utils/InternPool.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_UTILS_INTERNPOOL_H +#define TNT_UTILS_INTERNPOOL_H + +#include +#include +#include +#include +#include + +#include + +namespace utils { + +/** A reference-counted intern pool of slices of T. */ +template> +class InternPool { + struct HashSlice { + inline size_t operator()(Slice const& slice) const noexcept { + return slice.template hash(); + } + }; + + struct Entry { + uint32_t referenceCount; + FixedCapacityVector value; + }; + + using Map = tsl::robin_map, Entry, HashSlice>; + + static constexpr const char* UTILS_NONNULL MISSING_ENTRY_ERROR_STRING = + "InternPool is missing entry"; + +public: + InternPool() = default; + InternPool(InternPool const& rhs) = delete; + InternPool& operator=(InternPool const& rhs) = delete; + InternPool(InternPool&& rhs) = default; + InternPool& operator=(InternPool&& rhs) = default; + + /** Acquire an interned copy of value. */ + Slice acquire(Slice slice, size_t hash) noexcept { + if (slice.empty()) { + return { nullptr, nullptr }; + } + auto it = mMap.find(slice, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return it.key(); + } + FixedCapacityVector value(slice); + // TODO: how to use above computed hash here? + return mMap.insert({ value.as_slice(), Entry{ 1, std::move(value) } }).first.key(); + } + + inline Slice acquire(Slice slice) noexcept { + return acquire(slice, HashSlice{}(slice)); + } + + Slice acquire(FixedCapacityVector&& value, size_t hash) noexcept { + if (value.empty()) { + return { nullptr, nullptr }; + } + Slice slice = value.as_slice(); + auto it = mMap.find(slice, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return it.key(); + } + // TODO: how to use above computed hash here? + return mMap.insert({ slice, Entry{ 1, std::move(value) } }).first.key(); + } + + inline Slice acquire(FixedCapacityVector&& value) noexcept { + size_t hash = HashSlice{}(value.as_slice()); + return acquire(std::move(value), hash); + } + + inline Slice acquire(FixedCapacityVector const& value, size_t hash) noexcept { + return acquire(value.as_slice(), hash); + } + + inline Slice acquire(FixedCapacityVector const& value) noexcept { + Slice slice = value.as_slice(); + return acquire(slice, HashSlice{}(slice)); + } + + /** Release interned value. */ + void release(Slice slice, size_t hash) { + if (slice.empty()) { + return; + } + auto it = mMap.find(slice, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + // TODO: change to erase_fast + mMap.erase(it); + } + } + + inline void release(Slice slice) noexcept { + return release(slice, HashSlice{}(slice)); + } + + inline void release(FixedCapacityVector const& value, size_t hash) noexcept { + return release(value.as_slice(), hash); + } + + inline void release(FixedCapacityVector const& value) noexcept { + Slice slice = value.as_slice(); + return release(slice, HashSlice{}(slice)); + } + + /** Returns true if the pool is empty. */ + inline bool empty() const noexcept { return mMap.empty(); } + + /** Returns hash of value. */ + static size_t hash(Slice slice) noexcept { + return HashSlice{}(slice); + } + + static size_t hash(FixedCapacityVector const& value) noexcept { + return HashSlice{}(value.as_slice()); + } + +private: + Map mMap; +}; + +} // namespace utils + +#endif // TNT_UTILS_INTERNPOOL_H diff --git a/package/android/libs/filament/include/utils/Invocable.h b/package/android/libs/filament/include/utils/Invocable.h index 49b43071..08d42968 100644 --- a/package/android/libs/filament/include/utils/Invocable.h +++ b/package/android/libs/filament/include/utils/Invocable.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef TNT_UTILS_INVOKABLE_H -#define TNT_UTILS_INVOKABLE_H +#ifndef TNT_UTILS_INVOCABLE_H +#define TNT_UTILS_INVOCABLE_H #include #include @@ -23,6 +23,9 @@ #include namespace utils { +namespace io { +class ostream; +} /* * Invocable is a move-only general purpose function wrapper. Instances can @@ -48,15 +51,21 @@ template using EnableIfFnMatchesInvocable = std::enable_if_t; #endif +class InvocableBase { +protected: + static io::ostream& printInvocable(io::ostream& out, const char* name); +}; + template class Invocable; template -class Invocable { +class Invocable : protected InvocableBase { public: // Creates an Invocable that does not contain a functor. // Will evaluate to false. Invocable() = default; + Invocable(std::nullptr_t) noexcept {} ~Invocable() noexcept; @@ -69,6 +78,7 @@ class Invocable { Invocable& operator=(const Invocable&) = delete; Invocable& operator=(Invocable&& rhs) noexcept; + Invocable& operator=(std::nullptr_t) noexcept; // Invokes the invocable with the args passed in. // If the Invocable is empty, this will assert. @@ -81,6 +91,11 @@ class Invocable { explicit operator bool() const noexcept; private: +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const Invocable&) { + return printInvocable(out, "Invocable<>"); // TODO: is there a way to do better here? + } +#endif void* mInvocable = nullptr; void (*mDeleter)(void*) = nullptr; R (* mInvoker)(void*, Args...) = nullptr; @@ -128,6 +143,17 @@ Invocable& Invocable::operator=(Invocable&& rhs) noexcep return *this; } +template +Invocable& Invocable::operator=(std::nullptr_t) noexcept { + if (mDeleter) { + mDeleter(mInvocable); + } + mInvocable = nullptr; + mDeleter = nullptr; + mInvoker = nullptr; + return *this; +} + template template R Invocable::operator()(OperatorArgs&& ... args) { @@ -149,4 +175,4 @@ Invocable::operator bool() const noexcept { } // namespace utils -#endif // TNT_UTILS_INVOKABLE_H +#endif // TNT_UTILS_INVOCABLE_H diff --git a/package/android/libs/filament/include/utils/Logger.h b/package/android/libs/filament/include/utils/Logger.h new file mode 100644 index 00000000..b385c85b --- /dev/null +++ b/package/android/libs/filament/include/utils/Logger.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_LOGGER_H +#define TNT_UTILS_LOGGER_H + +// Logger.h provides a subset of the Abseil logging API, offering the following macros: + +// **LOG(severity)**: Logs a message at the specified severity level. +// **DLOG(severity)**: Logs a message at the specified severity level only in debug builds. + +// Supported `severity` levels are: +// * `INFO` +// * `WARNING` +// * `ERROR` +// * `FATAL` + +// For programmatic control over logging severity, use the `LEVEL` macro: + +// **LOG(LEVEL(expression))**: Logs a message at a severity level determined by the `expression`. +// The `expression` must return a `utils::LogSeverity` value, which is equivalent to +// `absl::LogSeverity`. + +#if defined(FILAMENT_USE_ABSEIL_LOGGING) + +#include +#include + +namespace utils { +using absl::LogSeverity; +} + +#else + +#include +#include + +namespace utils { + +enum class LogSeverity : int { + kInfo = 0, + kWarning = 1, + kError = 2, + kFatal = 3, +}; + +template +class LogLine { +public: + explicit LogLine(Stream& stream) + : mStream(stream) {} + + LogLine(const LogLine&) = delete; + LogLine(LogLine&&) = delete; + LogLine& operator=(const LogLine&) = delete; + LogLine& operator=(LogLine&&) = delete; + + ~LogLine() noexcept { mStream << io::endl; } + + template + LogLine& operator<<(T&& value) { + mStream << std::forward(value); + return *this; + } + +private: + Stream& mStream; +}; + +static inline io::ostream& getLogStream(LogSeverity severity) { + switch (severity) { + case LogSeverity::kInfo: + return slog.d; + case LogSeverity::kWarning: + return slog.w; + case LogSeverity::kError: + return slog.e; + case LogSeverity::kFatal: + return slog.e; + default: + return slog.d; + } +} + +struct NoopStream final { + template + NoopStream& operator<<(const T&) { + return *this; + } +}; + +#define LOG(severity) LOG_IMPL_##severity + +#ifndef NDEBUG +#define DLOG(severity) DLOG_IMPL_##severity +#else +#define DLOG(severity) utils::NoopStream() +#endif + +#define DLOG_IMPL_INFO utils::LogLine(utils::slog.d) +#define DLOG_IMPL_WARNING utils::LogLine(utils::slog.d) +#define DLOG_IMPL_ERROR utils::LogLine(utils::slog.d) +#define DLOG_IMPL_LEVEL(severity) utils::LogLine(((void) severity, utils::slog.d)) + +#define LOG_IMPL_INFO utils::LogLine(utils::slog.i) +#define LOG_IMPL_WARNING utils::LogLine(utils::slog.w) +#define LOG_IMPL_ERROR utils::LogLine(utils::slog.e) +#define LOG_IMPL_LEVEL(severity) utils::LogLine(getLogStream(severity)) + +}// namespace utils +#endif + +#endif// TNT_UTILS_LOGGER_H diff --git a/package/android/libs/filament/include/utils/MonotonicRingMap.h b/package/android/libs/filament/include/utils/MonotonicRingMap.h new file mode 100644 index 00000000..01ead770 --- /dev/null +++ b/package/android/libs/filament/include/utils/MonotonicRingMap.h @@ -0,0 +1,157 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_MONOTONICRINGMAP_H +#define TNT_UTILS_MONOTONICRINGMAP_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace utils { + +/** + * A map-like container with a fixed capacity and monotonically increasing keys. + * When the map is full, inserting a new element overwrites the oldest one. + * This container doesn't allocate any memory on the heap. + * Lookups are O(log N). + */ +template +class MonotonicRingMap { +public: + using key_type = KEY; //!< The key type. + using mapped_type = VALUE; //!< The value type. + using value_type = std::pair; //!< The key-value pair type. + + //! Creates an empty map. + MonotonicRingMap() noexcept = default; + + //! Returns the number of elements in the map. + size_t size() const noexcept { return mSize; } + + //! Returns the maximum number of elements the map can hold. + static constexpr size_t capacity() noexcept { return N; } + + //! Returns true if the map is empty. + bool empty() const noexcept { return mSize == 0; } + + //! Returns true if the map is full. + bool full() const noexcept { return mSize == N; } + + //! Clears the map entirely. + void clear() noexcept { mSize = 0; mHead = 0; } + + /** + * Inserts a new key-value pair. + * The key must be greater than the key of the last inserted element. + * If the map is full, the oldest element is overwritten. + * @param key The key to insert. + * @param value The value to associate with the key. + */ + UTILS_NOINLINE void insert(key_type key, mapped_type value) { + assert(empty() || key > back().first); // assert monotonic + if (UTILS_LIKELY(full())) { + // container is full, replace the oldest element + mStorage[mHead] = { key, value }; + mHead = (mHead + 1) % N; + } else { + // container is not full, add to the end + const uint32_t index = (mHead + mSize) % N; + mStorage[index] = { key, value }; + mSize++; + } + } + + /** + * Finds a value by its key. + * @param key The key to look for. + * @return A pointer to the value if the key is found, nullptr otherwise. + */ + mapped_type* find(key_type key) { + return const_cast(static_cast(this)->find(key)); + } + + /** + * Finds a value by its key. + * @param key The key to look for. + * @return A pointer to the const value if the key is found, nullptr otherwise. + */ + UTILS_NOINLINE const mapped_type* find(key_type key) const { + if (empty()) { + return nullptr; + } + + if (key < front().first || key > back().first) { + return nullptr; + } + + const auto comparator = [](const value_type& element, key_type k) { + return element.first < k; + }; + + const auto endOfStorage = mStorage.cbegin() + N; + const auto headIter = mStorage.cbegin() + mHead; + + if (mHead + mSize <= N) { + // The logical sequence is contiguous in memory + const auto logicalEnd = headIter + mSize; + auto it = std::lower_bound(headIter, logicalEnd, key, comparator); + if (it != logicalEnd && it->first == key) { + return &it->second; + } + } else { // Wrapped around + // First part: mStorage[mHead...N-1] + auto it1 = std::lower_bound(headIter, endOfStorage, key, comparator); + if (it1 != endOfStorage && it1->first == key) { + return &it1->second; + } + + // Second part: mStorage[0...head-1] + const auto wrapPartEndIter = mStorage.cbegin() + ((mHead + mSize) % N); + auto it2 = std::lower_bound(mStorage.cbegin(), wrapPartEndIter, key, comparator); + if (it2 != wrapPartEndIter && it2->first == key) { + return &it2->second; + } + } + return nullptr; + } + + //! Returns a reference to the oldest element. + const value_type& front() const { + assert(!empty()); + return mStorage[mHead]; + } + + //! Returns a reference to the newest element. + const value_type& back() const { + assert(!empty()); + return mStorage[(mHead + mSize - 1) % N]; + } + +private: + std::array mStorage; + uint32_t mSize = 0; + uint32_t mHead = 0; +}; + +} // namespace utils + +#endif // TNT_UTILS_MONOTONICRINGMAP_H diff --git a/package/android/libs/filament/include/utils/NameComponentManager.h b/package/android/libs/filament/include/utils/NameComponentManager.h index 4ac7435a..403cc011 100644 --- a/package/android/libs/filament/include/utils/NameComponentManager.h +++ b/package/android/libs/filament/include/utils/NameComponentManager.h @@ -47,7 +47,7 @@ class EntityManager; * printf("%s\n", names->getName(names->getInstance(myEntity)); * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager { +class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager { public: using Instance = EntityInstance; @@ -97,7 +97,7 @@ class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager const char* getName(Instance instance) const noexcept; void gc(EntityManager& em) noexcept { - SingleInstanceComponentManager::gc(em, [this](Entity e) { + SingleInstanceComponentManager::gc(em, [this](Entity e) { removeComponent(e); }); } diff --git a/package/android/libs/filament/include/utils/Panic.h b/package/android/libs/filament/include/utils/Panic.h index 6e9ac3f4..acd19468 100644 --- a/package/android/libs/filament/include/utils/Panic.h +++ b/package/android/libs/filament/include/utils/Panic.h @@ -353,7 +353,7 @@ class UTILS_PUBLIC Panic { * The TPanic<> class implements the std::exception protocol as well as the Panic * interface common to all exceptions thrown by the framework. */ -template +template class UTILS_PUBLIC TPanic : public Panic { public: // std::exception protocol @@ -377,7 +377,8 @@ class UTILS_PUBLIC TPanic : public Panic { * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected * @param literal a literal version of the error message - * @param format printf style string describing the error + * @param format printf style format string describing the error + * @param ... printf style arguments * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() @@ -398,12 +399,11 @@ class UTILS_PUBLIC TPanic : public Panic { * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() */ - static inline void panic( + static void panic( char const* function, char const* file, int line, char const* literal, std::string reason) UTILS_NORETURN; -protected: - +private: /** * Creates a Panic with extra information about the error-site. * @param function the name of the function where the error was detected @@ -415,11 +415,14 @@ class UTILS_PUBLIC TPanic : public Panic { TPanic(char const* function, char const* file, int line, char const* literal, std::string reason); + friend class PreconditionPanic; + friend class PostconditionPanic; + friend class ArithmeticPanic; + +protected: ~TPanic() override; private: - void buildMessage(); - char const* const mFile = nullptr; // file where the panic happened char const* const mFunction = nullptr; // function where the panic happened int const mLine = -1; // line where the panic happened @@ -443,11 +446,11 @@ void panicLog( * ASSERT_PRECONDITION uses this Panic to report a precondition failure. * @see ASSERT_PRECONDITION */ -class UTILS_PUBLIC PreconditionPanic : public TPanic { +class UTILS_PUBLIC PreconditionPanic final : public TPanic { // Programming error, can be avoided // e.g.: invalid arguments - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Precondition"; }; @@ -457,11 +460,11 @@ class UTILS_PUBLIC PreconditionPanic : public TPanic { * ASSERT_POSTCONDITION uses this Panic to report a postcondition failure. * @see ASSERT_POSTCONDITION */ -class UTILS_PUBLIC PostconditionPanic : public TPanic { +class UTILS_PUBLIC PostconditionPanic final : public TPanic { // Usually only detectable at runtime - // e.g.: dead-lock would occur, arithmetic errors - using TPanic::TPanic; - friend class TPanic; + // e.g.: deadlock would occur, arithmetic errors + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Postcondition"; }; @@ -471,11 +474,11 @@ class UTILS_PUBLIC PostconditionPanic : public TPanic { * ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure. * @see ASSERT_ARITHMETIC */ -class UTILS_PUBLIC ArithmeticPanic : public TPanic { +class UTILS_PUBLIC ArithmeticPanic final : public TPanic { // A common case of post-condition error // e.g.: underflow, overflow, internal computations errors - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Arithmetic"; }; @@ -519,11 +522,11 @@ class UTILS_PUBLIC PanicStream { PanicStream& operator<<(const void* value) noexcept; - PanicStream& operator<<(const char* string) noexcept; - PanicStream& operator<<(const unsigned char* string) noexcept; + PanicStream& operator<<(const char* value) noexcept; + PanicStream& operator<<(const unsigned char* value) noexcept; - PanicStream& operator<<(std::string const& s) noexcept; - PanicStream& operator<<(std::string_view const& s) noexcept; + PanicStream& operator<<(std::string const& value) noexcept; + PanicStream& operator<<(std::string_view const& value) noexcept; protected: io::sstream mStream; @@ -542,6 +545,27 @@ class TPanicStream : public PanicStream { } }; +template +class FlagGuardedStream : public PanicStream { +public: + FlagGuardedStream(bool const enable, char const* function, char const* file, int const line, + char const* condition) + : PanicStream(function, file, line, condition), + mEnablePanic(enable) {} + ~FlagGuardedStream() noexcept(false) { + if (mEnablePanic) { + PanicType::panic(mFunction, mFile, mLine, mLiteral, mStream.c_str()); + } else { + logWarning(); + } + } + +private: + void logWarning() noexcept; + + bool mEnablePanic; +}; + } // namespace details // ----------------------------------------------------------------------------------------------- @@ -560,7 +584,7 @@ class TPanicStream : public PanicStream { switch (0) \ case 0: \ default: \ - UTILS_LIKELY(cond) ? (void)0 : ::utils::details::Voidify()&& + UTILS_VERY_LIKELY(cond) ? (void)0 : ::utils::details::Voidify()&& #define FILAMENT_PANIC_IMPL(message, TYPE) \ ::utils::details::TPanicStream<::utils::TYPE>(PANIC_FUNCTION, PANIC_FILE(__FILE__), __LINE__, message) @@ -580,6 +604,20 @@ class TPanicStream : public PanicStream { FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, ArithmeticPanic) #endif +#define FILAMENT_FLAG_GUARDED_PANIC_IMPL(flag, condition, TYPE) \ + ::utils::details::FlagGuardedStream<::utils::TYPE>(flag, PANIC_FUNCTION, PANIC_FILE(__FILE__), \ + __LINE__, #condition) + +#ifndef FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION +#define FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION(condition, flag) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_FLAG_GUARDED_PANIC_IMPL(flag, condition, PreconditionPanic) +#endif + +#ifndef FILAMENT_FLAG_GUARDED_CHECK_POSTCONDITION +#define FILAMENT_FLAG_GUARDED_CHECK_POSTCONDITION(condition, flag) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_FLAG_GUARDED_PANIC_IMPL(flag, condition, PostconditionPanic) +#endif + #define PANIC_PRECONDITION_IMPL(cond, format, ...) \ ::utils::PreconditionPanic::panic(PANIC_FUNCTION, \ PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) @@ -629,14 +667,14 @@ class TPanicStream : public PanicStream { * @param format printf-style string describing the error in more details */ #define ASSERT_PRECONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) + (!UTILS_VERY_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif @@ -657,14 +695,14 @@ class TPanicStream : public PanicStream { * @endcode */ #define ASSERT_POSTCONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) + (!UTILS_VERY_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -689,10 +727,10 @@ class TPanicStream : public PanicStream { #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -717,6 +755,6 @@ class TPanicStream : public PanicStream { * @endcode */ #define ASSERT_DESTRUCTOR(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__) : (void)0) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #endif // TNT_UTILS_PANIC_H diff --git a/package/android/libs/filament/include/utils/RefCountedMap.h b/package/android/libs/filament/include/utils/RefCountedMap.h new file mode 100644 index 00000000..5fc0b85f --- /dev/null +++ b/package/android/libs/filament/include/utils/RefCountedMap.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_UTILS_REFCOUNTEDMAP_H +#define TNT_UTILS_REFCOUNTEDMAP_H + +#include +#include +#include + +#include + +#include +#include +#include + +namespace utils { + +namespace refcountedmap { + +template +struct is_pointer_like_trait : std::false_type {}; + +template +struct is_pointer_like_trait())>> : std::true_type {}; + +template +inline constexpr bool IsPointer = is_pointer_like_trait::value; + +template +struct PointerTraits { + using element_type = T; +}; + +template +struct PointerTraits>> { + using element_type = typename std::pointer_traits::element_type; +}; + +template +struct DefaultValue { + T operator()() const noexcept { + return {}; + } +}; + +} // namespace refcountedmap + +/** A reference-counted map. + * + * Don't use RAII here, both because we sometimes want to deliberately leak memory, and because + * we're managing GL resources that require more managed destruction. + */ +template, + typename NullValue = refcountedmap::DefaultValue> +class RefCountedMap { + // Use references for the key if the size of the key type is greater than the size of a pointer. + using KeyRef = std::conditional_t<(sizeof(Key) > sizeof(void*)), const Key&, Key>; + using TValue = typename refcountedmap::PointerTraits::element_type; + + struct Entry { + uint32_t referenceCount; + T value; + }; + + using Map = tsl::robin_map; + + static constexpr TValue& deref(T& a) { + if constexpr (refcountedmap::IsPointer) { + return *a; + } else { + return a; + } + } + + static constexpr TValue const& deref(T const& a) { + if constexpr (refcountedmap::IsPointer) { + return *a; + } else { + return a; + } + } + + static constexpr const char* UTILS_NONNULL MISSING_ENTRY_ERROR_STRING = + "Cache is missing entry"; + static constexpr const char* UTILS_NONNULL MISSING_VALUE_ERROR_STRING = + "Attempted to get missing value"; + +public: + /** Acquire and return a value by key, initializing it with F if it doesn't exist. + * + * If F returns NullValue{}(), this indicates a failure to create the object. If T is a value + * type, the returned pointer is valid only as long as the next call to acquire() or release(). + */ + template + TValue* UTILS_NULLABLE acquire(KeyRef key, size_t hash, F factory) noexcept { + auto it = mMap.find(key, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return &deref(it.value().value); + } + T r = factory(); + if (r == NullValue{}()) { + return nullptr; + } + // TODO: how to use above computed hash here? + return &deref(mMap.insert({ key, Entry{ 1, std::move(r) } }).first.value().value); + } + + template + inline TValue* UTILS_NULLABLE acquire(KeyRef key, F factory) noexcept { + return acquire(key, Hash{}(key), std::move(factory)); + } + + /** Acquire and return a pointer to the value if one exists. + * + * It's possible to acquire a key before its value is initialized, in which case this function + * returns nullptr. + * + * If T is a value type, this pointer is valid only as long as the next call to acquire() or + * release(). + */ + TValue* UTILS_NULLABLE acquire(KeyRef key, size_t hash) noexcept { + auto it = mMap.find(key, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return &deref(it.value().value); + } + // TODO: how to use above computed hash here? + mMap.insert({ key, Entry{ 1, NullValue{}() } }); + return nullptr; + } + + inline TValue* UTILS_NULLABLE acquire(KeyRef key) noexcept { + return acquire(key, Hash{}(key)); + } + + /** Release a reference to key, destroying it with F if reference count reaches zero. + * + * Panics if no entry found in map. + */ + template + void release(KeyRef key, size_t hash, F releaser) { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + if (it.value().value != NullValue{}()){ + releaser(deref(it.value().value)); + } + // TODO: change to erase_fast + mMap.erase(it); + } + } + + template + inline void release(KeyRef key, F releaser) noexcept { + release(key, Hash{}(key), std::move(releaser)); + } + + /** Release a reference to key. + * + * Panics if no entry found in map. + */ + void release(KeyRef key, size_t hash) { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + // TODO: change to erase_fast + mMap.erase(it); + } + } + + inline void release(KeyRef key) noexcept { + release(key, Hash{}(key)); + } + + /** Get a value by key, initializing it with F if it doesn't exist. + * + * If F returns NullValue{}(), this indicates a failure to create the object. If T is a value + * type, the returned pointer is valid only as long as the next call to acquire() or release(). + */ + template + TValue* UTILS_NULLABLE get(KeyRef key, size_t hash, F factory) { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + const T nullValue = NullValue{}(); + if (it.value().value == nullValue) { + it.value().value = factory(); + if (it.value().value == nullValue) { + return nullptr; + } + } + return &deref(it.value().value); + } + + template + inline TValue* UTILS_NULLABLE get(KeyRef key, F factory) noexcept { + return get(key, Hash{}(key), std::move(factory)); + } + + /** Return reference to existing value by key. + * + * This reference is valid only as long as the next call to acquire() or release(). + * + * Panics if no entry found in map. + */ + TValue& get(KeyRef key, size_t hash) { + auto it = mMap.find(key); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + FILAMENT_CHECK_PRECONDITION(it.value().value != NullValue{}()) + << MISSING_VALUE_ERROR_STRING; + return deref(it.value().value); + } + + inline TValue& get(KeyRef key) noexcept { return get(key, Hash{}(key)); } + + TValue const& get(KeyRef key, size_t hash) const { + auto it = mMap.find(key); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + FILAMENT_CHECK_PRECONDITION(it.value().value != NullValue{}()) + << MISSING_VALUE_ERROR_STRING; + return deref(it->second.value); + } + + inline TValue const& get(KeyRef key) const noexcept { return get(key, Hash{}(key)); } + + /** Returns true if the map is empty. */ + inline bool empty() const noexcept { return mMap.empty(); } + +private: + Map mMap; +}; + +} + +#endif // TNT_UTILS_REFCOUNTEDMAP_H diff --git a/package/android/libs/filament/include/utils/SingleInstanceComponentManager.h b/package/android/libs/filament/include/utils/SingleInstanceComponentManager.h index ddd538f5..fc820ca2 100644 --- a/package/android/libs/filament/include/utils/SingleInstanceComponentManager.h +++ b/package/android/libs/filament/include/utils/SingleInstanceComponentManager.h @@ -108,7 +108,7 @@ class UTILS_PUBLIC SingleInstanceComponentManager { return getComponentCount() == 0; } - utils::Entity const* getEntities() const noexcept { + Entity const* getEntities() const noexcept { return data() + 1; } @@ -238,11 +238,11 @@ class UTILS_PUBLIC SingleInstanceComponentManager { default_random_engine& rng = mRng; UTILS_NOUNROLL while (count && aliveInARow < ratio) { - assert_invariant(count == getComponentCount()); + assert(count == getComponentCount()); // note: using the modulo favorizes lower number size_t const i = rng() % count; Entity const entity = pEntities[i]; - assert_invariant(entity); + assert(entity); if (UTILS_LIKELY(em.isAlive(entity))) { ++aliveInARow; continue; diff --git a/package/android/libs/filament/include/utils/Slice.h b/package/android/libs/filament/include/utils/Slice.h index 444a3b27..59f1043d 100644 --- a/package/android/libs/filament/include/utils/Slice.h +++ b/package/android/libs/filament/include/utils/Slice.h @@ -17,8 +17,11 @@ #ifndef TNT_UTILS_SLICE_H #define TNT_UTILS_SLICE_H +#include #include +#include +#include #include #include @@ -26,116 +29,126 @@ namespace utils { -/* - * A fixed-size slice of a container +/** A fixed-size slice of a container. + * + * Analogous to std::span. */ -template +template class Slice { public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = size_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; using iterator = T*; using const_iterator = T const*; - using value_type = T; - using reference = T&; - using const_reference = T const&; - using pointer = T*; - using const_pointer = T const*; - using size_type = size_t; - Slice() noexcept = default; + Slice() = default; + Slice(iterator begin, iterator end) noexcept : mBegin(begin), mEnd(end) {} + Slice(pointer begin, size_type count) noexcept : mBegin(begin), mEnd(begin + count) {} - Slice(const_iterator begin, const_iterator end) noexcept - : mBegin(const_cast(begin)), - mEnd(const_cast(end)) { - } + Slice(Slice const& rhs) : mBegin(rhs.begin()), mEnd(rhs.end()) {} - Slice(const_pointer begin, size_type count) noexcept - : mBegin(const_cast(begin)), - mEnd(mBegin + count) { + // If Slice is Slice, define coercive copy constructor from Slice. + template + Slice(std::enable_if_t, Slice const&> rhs) + : mBegin(rhs.begin()), mEnd(rhs.end()) {} + + Slice& operator=(Slice const& rhs) noexcept { + mBegin = rhs.begin(); + mEnd = rhs.end(); + return *this; } - Slice(Slice const& rhs) noexcept = default; - Slice(Slice&& rhs) noexcept = default; - Slice& operator=(Slice const& rhs) noexcept = default; - Slice& operator=(Slice&& rhs) noexcept = default; + // If Slice is Slice, define assignment operator from Slice. + template + Slice& operator=( + std::enable_if_t, Slice const&> rhs) noexcept { + mBegin = rhs.begin(); + mEnd = rhs.end(); + return *this; + } - void set(pointer begin, size_type count) UTILS_RESTRICT noexcept { + void set(pointer begin, size_type count) noexcept { mBegin = begin; mEnd = begin + count; } - void set(iterator begin, iterator end) UTILS_RESTRICT noexcept { + void set(iterator begin, iterator end) noexcept { mBegin = begin; mEnd = end; } - void swap(Slice& rhs) UTILS_RESTRICT noexcept { + void swap(Slice& rhs) noexcept { std::swap(mBegin, rhs.mBegin); std::swap(mEnd, rhs.mEnd); } - void clear() UTILS_RESTRICT noexcept { + void clear() noexcept { mBegin = nullptr; mEnd = nullptr; } + bool operator==(Slice const& rhs) const noexcept { + if (size() != rhs.size()) { + return false; + } + if (mBegin == rhs.cbegin()) { + return true; + } + return std::equal(cbegin(), cend(), rhs.cbegin()); + } + + bool operator==(Slice const& rhs) const noexcept { + return *this == Slice(rhs); + } + // size - size_t size() const UTILS_RESTRICT noexcept { return mEnd - mBegin; } - size_t sizeInBytes() const UTILS_RESTRICT noexcept { return size() * sizeof(T); } - bool empty() const UTILS_RESTRICT noexcept { return size() == 0; } + size_t size() const noexcept { return mEnd - mBegin; } + size_t sizeInBytes() const noexcept { return size() * sizeof(T); } + bool empty() const noexcept { return size() == 0; } // iterators - iterator begin() UTILS_RESTRICT noexcept { return mBegin; } - const_iterator begin() const UTILS_RESTRICT noexcept { return mBegin; } - const_iterator cbegin() const UTILS_RESTRICT noexcept { return this->begin(); } - iterator end() UTILS_RESTRICT noexcept { return mEnd; } - const_iterator end() const UTILS_RESTRICT noexcept { return mEnd; } - const_iterator cend() const UTILS_RESTRICT noexcept { return this->end(); } + iterator begin() const noexcept { return mBegin; } + const_iterator cbegin() const noexcept { return this->begin(); } + iterator end() const noexcept { return mEnd; } + const_iterator cend() const noexcept { return this->end(); } // data access - reference operator[](size_t n) UTILS_RESTRICT noexcept { + reference operator[](size_t n) const noexcept { assert(n < size()); return mBegin[n]; } - const_reference operator[](size_t n) const UTILS_RESTRICT noexcept { - assert(n < size()); - return mBegin[n]; - } - - reference at(size_t n) UTILS_RESTRICT noexcept { + reference at(size_t n) const noexcept { return operator[](n); } - const_reference at(size_t n) const UTILS_RESTRICT noexcept { - return operator[](n); - } - - reference front() UTILS_RESTRICT noexcept { + reference front() const noexcept { assert(!empty()); return *mBegin; } - const_reference front() const UTILS_RESTRICT noexcept { - assert(!empty()); - return *mBegin; - } - - reference back() UTILS_RESTRICT noexcept { + reference back() const noexcept { assert(!empty()); return *(this->end() - 1); } - const_reference back() const UTILS_RESTRICT noexcept { - assert(!empty()); - return *(this->end() - 1); - } - - pointer data() UTILS_RESTRICT noexcept { + pointer data() const noexcept { return this->begin(); } - const_pointer data() const UTILS_RESTRICT noexcept { - return this->begin(); + template> + size_t hash() const noexcept { + Hash hasher; + size_t seed = size(); + for (auto const& it : *this) { + utils::hash::combine_fast(seed, hasher(it)); + } + return seed; } protected: @@ -145,4 +158,13 @@ class Slice { } // namespace utils +namespace std { + +template +struct hash> { + inline size_t operator()(utils::Slice const& lhs) const noexcept { return lhs.hash(); } +}; + +} // namespace std + #endif // TNT_UTILS_SLICE_H diff --git a/package/android/libs/filament/include/utils/StaticString.h b/package/android/libs/filament/include/utils/StaticString.h new file mode 100644 index 00000000..ae2008cb --- /dev/null +++ b/package/android/libs/filament/include/utils/StaticString.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_STATICSTRING_H +#define TNT_UTILS_STATICSTRING_H + +#include + +#include + +#include + +namespace utils { + +class ImmutableCString; + +/** + * @brief A lightweight string class that stores a pointer to a string literal and its size, without dynamic allocation. + * + * This class is designed to efficiently represent string literals. It does not allocate any memory + * and instead relies on the compiler to manage the memory of the string literals. + */ +class UTILS_PUBLIC StaticString { +public: + using value_type = std::string_view::value_type; + using const_pointer = std::string_view::const_pointer; + using const_reference = std::string_view::const_reference; + using size_type = std::string_view::size_type; + using const_iterator = std::string_view::const_iterator; + + // Constructor from string literal + template + constexpr StaticString(const char (&str)[M]) noexcept : mString(str, M - 1) {} // NOLINT(*-explicit-constructor) + + constexpr StaticString() noexcept : mString("", 0) {} + + constexpr const_pointer c_str() const noexcept { return mString.data(); } + constexpr const_pointer data() const noexcept { return mString.data(); } + constexpr size_type size() const noexcept { return mString.size(); } + constexpr size_type length() const noexcept { return mString.size(); } + constexpr bool empty() const noexcept { return mString.empty(); } + + constexpr const_iterator begin() const noexcept { return mString.begin(); } + constexpr const_iterator end() const noexcept { return mString.end(); } + constexpr const_iterator cbegin() const noexcept { return mString.begin(); } + constexpr const_iterator cend() const noexcept { return mString.end(); } + + constexpr const_reference operator[](size_type const pos) const noexcept { + return mString[pos]; + } + + constexpr const_reference at(size_type const pos) const { + return mString[pos]; + } + + constexpr const_reference front() const noexcept { + return mString.front(); + } + + constexpr const_reference back() const noexcept { + return mString.back(); + } + + constexpr int compare(const StaticString& rhs) const noexcept { + return mString.compare(rhs.mString); + } + +private: +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const ImmutableCString& rhs); +#endif + + std::string_view mString; + + friend constexpr bool operator==(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString == rhs.mString; + } + + friend constexpr bool operator!=(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString != rhs.mString; + } + + friend constexpr bool operator<(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString < rhs.mString; + } + + friend constexpr bool operator>(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString > rhs.mString; + } + + friend constexpr bool operator<=(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString <= rhs.mString; + } + + friend constexpr bool operator>=(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString >= rhs.mString; + } +}; + +} // namespace utils + +#endif // TNT_UTILS_STATICSTRING_H diff --git a/package/android/libs/filament/include/utils/Status.h b/package/android/libs/filament/include/utils/Status.h new file mode 100644 index 00000000..22a8eb4f --- /dev/null +++ b/package/android/libs/filament/include/utils/Status.h @@ -0,0 +1,162 @@ +/* +* Copyright (C) 2025 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef TNT_UTILS_STATUS_H +#define TNT_UTILS_STATUS_H + +#include +#include +#include +#include + +namespace utils { + +/** + * A code indicating the success or failure of an operation. + */ +enum class StatusCode { + /** The operation completed successfully. */ + OK, + /** The caller provided invalid arguments in the request. */ + INVALID_ARGUMENT, + /** Internal error was occurred while processing the request. */ + INTERNAL, + /** The requested operation is not supported. */ + UNSUPPORTED, +}; + +/** + * Returns the StatusCode to indicate whether the request was successful. + * If successful, it returns OK with an optional message, if not it returns + * other codes with an optional error message. + */ +class UTILS_PUBLIC Status { +public: + /** + * Creates a new Status with a StatusCode of OK. + */ + Status() : mStatusCode(StatusCode::OK) {} + + /** + * Creates a new Status with the given status code and supplementary message. + * + * @param statusCode The status code to use. + * @param message An optional message, usually contains the reason for the failure. + */ + Status(StatusCode statusCode, std::string_view message) : + mStatusCode(statusCode), + mMessage(message.data(), message.length()) {} + + Status(const Status& other) = default; + + Status(Status&& other) noexcept = default; + + ~Status() = default; + + Status& operator=(const Status& other) = default; + Status& operator=(Status&& other) noexcept = default; + + bool operator==(const Status& other) const { + return mStatusCode == other.mStatusCode && mMessage == other.mMessage; + } + + bool operator!=(const Status& other) const { + return !(*this == other); + } + + /** + * Returns true if the status is OK. + * @return true if the operation was successful, false otherwise. + */ + bool isOk() const { + return mStatusCode == StatusCode::OK; + } + + /** + * Returns the StatusCode for this Status. + * @return the StatusCode for this Status. + */ + StatusCode getCode() const { + return mStatusCode; + } + + /** + * Returns the message for this Status. + * @return The message string. Can be empty if it's not set. + */ + std::string_view getMessage() const { + return std::string_view(mMessage.begin(), mMessage.length()); + } + + /** + * Convenient factory functions for creating Status objects. + * Example usage: `return utils::Status::internal("internal error");` + */ + + /** + * Creates a success Status with a StatusCode of OK. + * @return a success Status with a StatusCode of OK + */ + static Status ok() { + return {}; + } + + /** + * Creates a success Status with a StatusCode of OK with a supplementary message. + * @return a success Status with a StatusCode of OK with a supplementary message. + */ + static Status ok(std::string_view message) { + return {StatusCode::OK, message}; + } + + /** + * Creates an error Status with an INTERNAL status code. + * @param message The error message to include. + * @return an error Status with an INTERNAL status code. + */ + static Status internal(std::string_view message) { + return {StatusCode::INTERNAL, message}; + } + + /** + * Creates an error Status with an INVALID_ARGUMENT status code. + * @param message The error message to include. + * @return an error Status with an INVALID_ARGUMENT status code. + */ + static Status invalidArgument(std::string_view message) { + return {StatusCode::INVALID_ARGUMENT, message}; + } + + /** + * Creates an error Status with an UNSUPPORTED status code. + * @param message The error message to include. + * @return an error Status with an UNSUPPORTED status code. + */ + static Status unsupported(std::string_view message) { + return { StatusCode::UNSUPPORTED, message }; + } + +private: + StatusCode mStatusCode; + // Additional message for the Status. Usually contains the reason for the error. + utils::CString mMessage; +}; + +utils::io::ostream& operator<<(utils::io::ostream& os, const Status& status); + +} // namespace utils + +#endif // TNT_UTILS_STATUS_H diff --git a/package/android/libs/filament/include/utils/StructureOfArrays.h b/package/android/libs/filament/include/utils/StructureOfArrays.h index c0b2315e..923744be 100644 --- a/package/android/libs/filament/include/utils/StructureOfArrays.h +++ b/package/android/libs/filament/include/utils/StructureOfArrays.h @@ -20,14 +20,8 @@ #include #include #include -#include #include -#include -#include -#include -#include - #include #include // note: this is safe, see how std::array is used below (inline / private) #include @@ -35,6 +29,12 @@ #include #include +#include +#include +#include +#include +#include + namespace utils { template @@ -130,7 +130,7 @@ class StructureOfArraysBase { friend class IteratorValueRef; friend iterator; friend const_iterator; - using Type = std::tuple::type...>; + using Type = std::tuple...>; Type elements; template @@ -535,29 +535,29 @@ class StructureOfArraysBase { private: template - inline typename std::enable_if::type + inline std::enable_if_t for_each(std::tuple&, FuncT) {} template - inline typename std::enable_if::type + inline std::enable_if_t for_each(std::tuple& t, FuncT f) { f(I, std::get(t)); for_each(t, f); } template - inline typename std::enable_if::type + inline std::enable_if_t for_each_index(std::tuple&, FuncT) {} template - inline typename std::enable_if::type + inline std::enable_if_t for_each_index(std::tuple& t, FuncT f) { f.template operator()(std::get(t)); for_each_index(t, f); } inline void resizeNoCheck(size_t needed) noexcept { - assert_invariant(mCapacity >= needed); + assert(mCapacity >= needed); if (needed < mSize) { // we shrink the arrays destroy_each(needed, mSize); @@ -590,14 +590,14 @@ class StructureOfArraysBase { size_t unalignment = (offsets[i - 1] + sizes[i - 1]) % alignments[i]; size_t alignment = unalignment ? (alignments[i] - unalignment) : 0; offsets[i] = offsets[i - 1] + (sizes[i - 1] + alignment); - assert_invariant(offsets[i] % alignments[i] == 0); + assert(offsets[i] % alignments[i] == 0); } return offsets; } void construct_each(size_t from, size_t to) noexcept { forEach([from, to](auto p) { - using T = typename std::decay::type; + using T = std::decay_t; // note: scalar types like int/float get initialized to zero if constexpr (!std::is_trivially_default_constructible_v) { for (size_t i = from; i < to; i++) { @@ -609,7 +609,7 @@ class StructureOfArraysBase { void destroy_each(size_t from, size_t to) noexcept { forEach([from, to](auto p) { - using T = typename std::decay::type; + using T = std::decay_t; if constexpr (!std::is_trivially_destructible_v) { for (size_t i = from; i < to; i++) { p[i].~T(); @@ -624,7 +624,7 @@ class StructureOfArraysBase { if (mSize) { auto size = mSize; // placate a compiler warning forEach([buffer, &index, &offsets, size](auto p) { - using T = typename std::decay::type; + using T = std::decay_t; T* UTILS_RESTRICT b = static_cast(buffer); // go through each element and move them from the old array to the new @@ -671,7 +671,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::operator=( - StructureOfArraysBase::IteratorValueRef const& rhs) { + IteratorValueRef const& rhs) { return operator=(IteratorValue(rhs)); } @@ -679,7 +679,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::operator=( - StructureOfArraysBase::IteratorValueRef&& rhs) noexcept { + IteratorValueRef&& rhs) noexcept { return operator=(IteratorValue(rhs)); } @@ -688,7 +688,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::assign( - StructureOfArraysBase::IteratorValue const& rhs, std::index_sequence) { + IteratorValue const& rhs, std::index_sequence) { // implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue const& rhs) auto UTILS_UNUSED l = { (soa->elementAt(index) = std::get(rhs.elements), 0)... }; return *this; @@ -699,7 +699,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::assign( - StructureOfArraysBase::IteratorValue&& rhs, std::index_sequence) noexcept { + IteratorValue&& rhs, std::index_sequence) noexcept { // implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue&& rhs) noexcept auto UTILS_UNUSED l = { (soa->elementAt(index) = std::move(std::get(rhs.elements)), 0)... }; diff --git a/package/android/libs/filament/include/utils/Systrace.h b/package/android/libs/filament/include/utils/Systrace.h index 9f5a7f2a..cc99ad39 100644 --- a/package/android/libs/filament/include/utils/Systrace.h +++ b/package/android/libs/filament/include/utils/Systrace.h @@ -14,10 +14,16 @@ * limitations under the License. */ +/*********************************************************************** + * DEPRECATED * + * * + * Systrace.h is deprecated as a public API. There is no replacement. * + * Applications should instead use the Perfetto SDK directly. * + ***********************************************************************/ + #ifndef TNT_UTILS_SYSTRACE_H #define TNT_UTILS_SYSTRACE_H - #define SYSTRACE_TAG_NEVER (0) #define SYSTRACE_TAG_ALWAYS (1<<0) #define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles diff --git a/package/android/libs/filament/include/utils/algorithm.h b/package/android/libs/filament/include/utils/algorithm.h index 7a747b84..c4f400f4 100644 --- a/package/android/libs/filament/include/utils/algorithm.h +++ b/package/android/libs/filament/include/utils/algorithm.h @@ -39,7 +39,7 @@ constexpr inline T popcount(T v) noexcept { return (T) (v * (ONES / 255)) >> (sizeof(T) - 1) * CHAR_BIT; } -template::value>> +template>> constexpr inline T clz(T x) noexcept { static_assert(sizeof(T) * CHAR_BIT <= 128, "details::clz() only support up to 128 bits"); x |= (x >> 1u); @@ -62,7 +62,7 @@ constexpr inline T clz(T x) noexcept { return T(sizeof(T) * CHAR_BIT) - details::popcount(x); } -template::value>> +template>> constexpr inline T ctz(T x) noexcept { static_assert(sizeof(T) * CHAR_BIT <= 64, "details::ctz() only support up to 64 bits"); T c = sizeof(T) * CHAR_BIT; @@ -227,7 +227,7 @@ unsigned long long UTILS_ALWAYS_INLINE popcount(unsigned long long x) noexcept { } template::value && std::is_unsigned::value>> + typename = std::enable_if_t && std::is_unsigned_v>> constexpr inline UTILS_PUBLIC UTILS_PURE T log2i(T x) noexcept { return (sizeof(x) * 8 - 1u) - clz(x); diff --git a/package/android/libs/filament/include/utils/bitset.h b/package/android/libs/filament/include/utils/bitset.h index 8844fdb8..314ad2b8 100644 --- a/package/android/libs/filament/include/utils/bitset.h +++ b/package/android/libs/filament/include/utils/bitset.h @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -27,6 +26,7 @@ #include // for std::fill #include +#include #include #if defined(__ARM_NEON) @@ -45,8 +45,8 @@ namespace utils { */ template::value && - std::is_unsigned::value>::type> + typename = std::enable_if_t && + std::is_unsigned_v>> class UTILS_PUBLIC bitset { T storage[N]; @@ -66,12 +66,12 @@ class UTILS_PUBLIC bitset { } T getBitsAt(size_t n) const noexcept { - assert_invariant(n::max(); + } + size_t size() const noexcept { return N * BITS_PER_WORD; } bool empty() const noexcept { return none(); } @@ -104,23 +115,23 @@ class UTILS_PUBLIC bitset { bool test(size_t bit) const noexcept { return operator[](bit); } void set(size_t b) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] |= T(1) << (b % BITS_PER_WORD); } void set(size_t b, bool value) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD)); storage[b / BITS_PER_WORD] |= T(value) << (b % BITS_PER_WORD); } void unset(size_t b) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD)); } void flip(size_t b) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] ^= T(1) << (b % BITS_PER_WORD); } @@ -133,7 +144,7 @@ class UTILS_PUBLIC bitset { } bool operator[](size_t b) const noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); return bool(storage[b / BITS_PER_WORD] & (T(1) << (b % BITS_PER_WORD))); } diff --git a/package/android/libs/filament/include/utils/compiler.h b/package/android/libs/filament/include/utils/compiler.h index 710c901e..79ee3dda 100644 --- a/package/android/libs/filament/include/utils/compiler.h +++ b/package/android/libs/filament/include/utils/compiler.h @@ -33,7 +33,7 @@ #if __has_attribute(visibility) # define UTILS_PUBLIC __attribute__((visibility("default"))) #else -# define UTILS_PUBLIC +# define UTILS_PUBLIC #endif #if __has_attribute(deprecated) @@ -104,6 +104,19 @@ # define UTILS_UNLIKELY( exp ) (!!(exp)) #endif +#if __has_builtin(__builtin_expect_with_probability) +# ifdef __cplusplus +# define UTILS_VERY_LIKELY( exp ) (__builtin_expect_with_probability( !!(exp), true, 0.995 )) +# define UTILS_VERY_UNLIKELY( exp ) (__builtin_expect_with_probability( !!(exp), false, 0.995 )) +# else +# define UTILS_VERY_LIKELY( exp ) (__builtin_expect_with_probability( !!(exp), 1, 0.995 )) +# define UTILS_VERY_UNLIKELY( exp ) (__builtin_expect_with_probability( !!(exp), 0, 0.995 )) +# endif +#else +# define UTILS_VERY_LIKELY( exp ) (!!(exp)) +# define UTILS_VERY_UNLIKELY( exp ) (!!(exp)) +#endif + #if __has_builtin(__builtin_prefetch) # define UTILS_PREFETCH( exp ) (__builtin_prefetch(exp)) #else @@ -251,7 +264,7 @@ typedef SSIZE_T ssize_t; #if defined(_MSC_VER) && !defined(__PRETTY_FUNCTION__) # define __PRETTY_FUNCTION__ __FUNCSIG__ -#endif +#endif #if defined(_MSC_VER) diff --git a/package/android/libs/filament/include/utils/debug.h b/package/android/libs/filament/include/utils/debug.h index 0f6ecdb2..7587c12f 100644 --- a/package/android/libs/filament/include/utils/debug.h +++ b/package/android/libs/filament/include/utils/debug.h @@ -28,7 +28,7 @@ void panic(const char *func, const char * file, int line, const char *assertion) # define assert_invariant(e) ((void)0) #else # define assert_invariant(e) \ - (UTILS_LIKELY(e) ? ((void)0) : utils::panic(__func__, __FILE__, __LINE__, #e)) + (UTILS_VERY_LIKELY(e) ? ((void)0) : utils::panic(__func__, __FILE__, __LINE__, #e)) #endif // NDEBUG #endif // TNT_UTILS_DEBUG_H diff --git a/package/android/libs/filament/include/utils/memalign.h b/package/android/libs/filament/include/utils/memalign.h index 4c048bff..a7043310 100644 --- a/package/android/libs/filament/include/utils/memalign.h +++ b/package/android/libs/filament/include/utils/memalign.h @@ -32,7 +32,7 @@ namespace utils { inline void* aligned_alloc(size_t size, size_t align) noexcept { // 'align' must be a power of two and a multiple of sizeof(void*) align = (align < sizeof(void*)) ? sizeof(void*) : align; - assert(align && !(align & align - 1)); + assert(align && !(align & (align - 1))); assert((align % sizeof(void*)) == 0); void* p = nullptr; @@ -40,7 +40,7 @@ inline void* aligned_alloc(size_t size, size_t align) noexcept { #if defined(WIN32) p = ::_aligned_malloc(size, align); #else - ::posix_memalign(&p, align, size); + (void) ::posix_memalign(&p, align, size); #endif return p; } @@ -73,8 +73,8 @@ class STLAlignedAllocator { using const_pointer = const TYPE*; using reference = TYPE&; using const_reference = const TYPE&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; + using size_type = ::size_t; + using difference_type = ::ptrdiff_t; using propagate_on_container_move_assignment = std::true_type; using is_always_equal = std::true_type; diff --git a/package/android/libs/filament/include/utils/ostream.h b/package/android/libs/filament/include/utils/ostream.h index cde8e75b..ea4952cc 100644 --- a/package/android/libs/filament/include/utils/ostream.h +++ b/package/android/libs/filament/include/utils/ostream.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,7 @@ namespace utils::io { struct ostream_; -class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { +class UTILS_PUBLIC ostream : protected PrivateImplementation { friend struct ostream_; public: @@ -95,11 +96,11 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { size_t length() const noexcept; private: - void reserve(size_t newSize) noexcept; + void reserve(size_t newCapacity) noexcept; char* buffer = nullptr; // buffer address char* curr = nullptr; // current pointer - size_t size = 0; // size remaining + size_t sizeRemaining = 0; // size remaining size_t capacity = 0; // total capacity of the buffer }; @@ -122,11 +123,6 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { const char* getFormat(type t) const noexcept; }; -// handles utils::bitset -inline ostream& operator << (ostream& o, utils::bitset32 const& s) noexcept { - return o << (void*)uintptr_t(s.getValue()); -} - // handles vectors from libmath (but we do this generically, without needing a dependency on libmath) template class VECTOR, typename T> inline ostream& operator<<(ostream& stream, const VECTOR& v) { @@ -144,4 +140,21 @@ inline ostream& endl(ostream& s) noexcept { return flush(s << '\n'); } } // namespace utils::io +namespace utils { + +// handles utils::bitset + +namespace io { +class ostream; +} + +inline std::ostream& operator<<(std::ostream& o, bitset32 const& s) noexcept { + return o << (void*) uintptr_t(s.getValue()); +} +inline io::ostream& operator<<(io::ostream& o, bitset32 const& s) noexcept { + return o << (void*) uintptr_t(s.getValue()); +} +}// namespace utils + + #endif // TNT_UTILS_OSTREAM_H diff --git a/package/android/libs/filament/include/viewer/AutomationEngine.h b/package/android/libs/filament/include/viewer/AutomationEngine.h index 8747f59d..3c7257f2 100644 --- a/package/android/libs/filament/include/viewer/AutomationEngine.h +++ b/package/android/libs/filament/include/viewer/AutomationEngine.h @@ -56,6 +56,22 @@ class UTILS_PUBLIC AutomationEngine { * Allows users to toggle screenshots, change the sleep duration between tests, etc. */ struct Options { + + /** + * Formats that could be used for exporting the screenshots. + */ + enum class ExportFormat : uint8_t { + /** + * Tagged Image File Format (TIFF) + */ + TIFF = 0, + + /** + * Netpbm color image format (Portable Pixel Map) + */ + PPM = 1, + }; + /** * Minimum time that automation waits between applying a settings object and advancing * to the next test case. Specified in seconds. @@ -82,6 +98,11 @@ class UTILS_PUBLIC AutomationEngine { * If true, the tick function writes out a settings JSON file before advancing. */ bool exportSettings = false; + + /** + * Which image format will be used for exporting screenshots. + */ + ExportFormat exportFormat = ExportFormat::TIFF; }; /** @@ -224,6 +245,9 @@ class UTILS_PUBLIC AutomationEngine { */ static void exportSettings(const Settings& settings, const char* filename); + static void exportScreenshot(View* view, Renderer* renderer, std::string filename, + bool autoclose, AutomationEngine* automationEngine); + Options getOptions() const { return mOptions; } bool isRunning() const { return mIsRunning; } size_t currentTest() const { return mCurrentTest; } diff --git a/package/android/libs/filament/include/viewer/Settings.h b/package/android/libs/filament/include/viewer/Settings.h index 2094296b..b16ee8ae 100644 --- a/package/android/libs/filament/include/viewer/Settings.h +++ b/package/android/libs/filament/include/viewer/Settings.h @@ -51,6 +51,7 @@ struct Settings; struct ViewSettings; struct LightSettings; struct ViewerOptions; +struct DebugOptions; enum class ToneMapping : uint8_t { LINEAR = 0, @@ -88,6 +89,8 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight* const utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene, View* view); void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera, Skybox* skybox, Renderer* renderer); +void applySettings(Engine* engine, const DebugOptions& settings, + Renderer* renderer); // Creates a new ColorGrading object based on the given settings. UTILS_PUBLIC @@ -246,11 +249,16 @@ struct ViewerOptions { bool autoInstancingEnabled = false; }; +struct DebugOptions { + uint16_t skipFrames = 0; +}; + struct Settings { ViewSettings view; MaterialSettings material; LightSettings lighting; ViewerOptions viewer; + DebugOptions debug; }; } // namespace viewer diff --git a/package/android/src/main/cpp/RNFFilament.cpp b/package/android/src/main/cpp/RNFFilament.cpp index accdd9b5..5a871a44 100644 --- a/package/android/src/main/cpp/RNFFilament.cpp +++ b/package/android/src/main/cpp/RNFFilament.cpp @@ -7,8 +7,11 @@ #include "RNFJSurfaceProvider.h" #include #include +#include JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + filament::VirtualMachineEnv::JNI_OnLoad(vm); + return facebook::jni::initialize(vm, [] { margelo::FilamentInstaller::registerNatives(); margelo::JFilamentProxy::registerNatives(); diff --git a/package/cpp/core/RNFEngineConfigHelper.cpp b/package/cpp/core/RNFEngineConfigHelper.cpp index d78e4869..5322b222 100644 --- a/package/cpp/core/RNFEngineConfigHelper.cpp +++ b/package/cpp/core/RNFEngineConfigHelper.cpp @@ -31,8 +31,8 @@ Engine::Config EngineConfigHelper::makeConfigFromUserParams(std::optional(params["jobSystemThreadCount"]); } - if (params.find("textureUseAfterFreePoolSize") != params.end()) { - config.textureUseAfterFreePoolSize = static_cast(params["textureUseAfterFreePoolSize"]); + if (params.find("disableHandleUseAfterFreeCheck") != params.end()) { + config.disableHandleUseAfterFreeCheck = static_cast(params["disableHandleUseAfterFreeCheck"]); } if (params.find("resourceAllocatorCacheMaxAge") != params.end()) { config.resourceAllocatorCacheMaxAge = static_cast(params["resourceAllocatorCacheMaxAge"]); diff --git a/package/ios/libs/filament/README.md b/package/ios/libs/filament/README.md index 79ec596b..abe815eb 100644 --- a/package/ios/libs/filament/README.md +++ b/package/ios/libs/filament/README.md @@ -92,14 +92,14 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory. ### Linux ```make -FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl +FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil CC=clang++ main: main.o $(CC) -Llib/x86_64/ main.o $(FILAMENT_LIBS) -lpthread -lc++ -ldl -o main main.o: main.cpp - $(CC) -Iinclude/ -std=c++17 -pthread -c main.cpp + $(CC) -Iinclude/ -std=c++20 -pthread -c main.cpp clean: rm -f main main.o @@ -110,15 +110,16 @@ clean: ### macOS ```make -FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl +FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil FRAMEWORKS=-framework Cocoa -framework Metal -framework CoreVideo CC=clang++ +ARCH ?= $(shell uname -m) main: main.o - $(CC) -Llib/x86_64/ main.o $(FILAMENT_LIBS) $(FRAMEWORKS) -o main + $(CC) -Llib/$(ARCH)/ main.o $(FILAMENT_LIBS) $(FRAMEWORKS) -o main main.o: main.cpp - $(CC) -Iinclude/ -std=c++17 -c main.cpp + $(CC) -Iinclude/ -std=c++20 -c main.cpp clean: rm -f main main.o @@ -139,7 +140,7 @@ used to change the run-time library version. ```make FILAMENT_LIBS=filament.lib backend.lib bluegl.lib bluevk.lib filabridge.lib filaflat.lib \ - utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib + utils.lib geometry.lib smol-v.lib ibl.lib abseil.lib CC=cl.exe main.exe: main.obj @@ -147,7 +148,7 @@ main.exe: main.obj gdi32.lib user32.lib opengl32.lib main.obj: main.cpp - $(CC) /MT /Iinclude\\ /std:c++17 /c main.cpp + $(CC) /MT /Iinclude\\ /std:c++20 /c main.cpp clean: del main.exe main.obj diff --git a/package/ios/libs/filament/include/backend/AcquiredImage.h b/package/ios/libs/filament/include/backend/AcquiredImage.h index fec27a53..ff0840e5 100644 --- a/package/ios/libs/filament/include/backend/AcquiredImage.h +++ b/package/ios/libs/filament/include/backend/AcquiredImage.h @@ -18,6 +18,7 @@ #define TNT_FILAMENT_BACKEND_PRIVATE_ACQUIREDIMAGE_H #include +#include namespace filament::backend { diff --git a/package/ios/libs/filament/include/backend/BufferDescriptor.h b/package/ios/libs/filament/include/backend/BufferDescriptor.h index ebb57537..afc0da20 100644 --- a/package/ios/libs/filament/include/backend/BufferDescriptor.h +++ b/package/ios/libs/filament/include/backend/BufferDescriptor.h @@ -20,10 +20,15 @@ #define TNT_FILAMENT_BACKEND_BUFFERDESCRIPTOR_H #include -#include + +#include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { class CallbackHandler; @@ -89,8 +94,8 @@ class UTILS_PUBLIC BufferDescriptor { * @param callback A callback used to release the CPU buffer from this BufferDescriptor * @param user An opaque user pointer passed to the callback function when it's called */ - BufferDescriptor(void const* buffer, size_t size, - Callback callback = nullptr, void* user = nullptr) noexcept + BufferDescriptor(void const* buffer, size_t const size, + Callback const callback = nullptr, void* user = nullptr) noexcept : buffer(const_cast(buffer)), size(size), mCallback(callback), mUser(user) { } @@ -98,11 +103,12 @@ class UTILS_PUBLIC BufferDescriptor { * Creates a BufferDescriptor that references a CPU memory-buffer * @param buffer Memory address of the CPU buffer to reference * @param size Size of the CPU buffer in bytes + * @param handler A custom handler for the callback * @param callback A callback used to release the CPU buffer from this BufferDescriptor * @param user An opaque user pointer passed to the callback function when it's called */ - BufferDescriptor(void const* buffer, size_t size, - CallbackHandler* handler, Callback callback, void* user = nullptr) noexcept + BufferDescriptor(void const* buffer, size_t const size, + CallbackHandler* handler, Callback const callback, void* user = nullptr) noexcept : buffer(const_cast(buffer)), size(size), mCallback(callback), mUser(user), mHandler(handler) { } @@ -116,8 +122,9 @@ class UTILS_PUBLIC BufferDescriptor { * * @param buffer Memory address of the CPU buffer to reference * @param size Size of the CPU buffer in bytes + * @param data A pointer to the data * @param handler Handler to use to dispatch the callback, or nullptr for the default handler - * @return a new BufferDescriptor + * @return A new BufferDescriptor */ template static BufferDescriptor make(void const* buffer, size_t size, T* data, @@ -164,7 +171,7 @@ class UTILS_PUBLIC BufferDescriptor { * @param callback The new callback function * @param user An opaque user pointer passed to the callbeck function when it's called */ - void setCallback(Callback callback, void* user = nullptr) noexcept { + void setCallback(Callback const callback, void* user = nullptr) noexcept { this->mCallback = callback; this->mUser = user; this->mHandler = nullptr; @@ -176,7 +183,7 @@ class UTILS_PUBLIC BufferDescriptor { * @param callback The new callback function * @param user An opaque user pointer passed to the callbeck function when it's called */ - void setCallback(CallbackHandler* handler, Callback callback, void* user = nullptr) noexcept { + void setCallback(CallbackHandler* handler, Callback const callback, void* user = nullptr) noexcept { mCallback = callback; mUser = user; mHandler = handler; diff --git a/package/ios/libs/filament/include/backend/CallbackHandler.h b/package/ios/libs/filament/include/backend/CallbackHandler.h index 036031a9..9700186d 100644 --- a/package/ios/libs/filament/include/backend/CallbackHandler.h +++ b/package/ios/libs/filament/include/backend/CallbackHandler.h @@ -64,7 +64,7 @@ class CallbackHandler { virtual void post(void* user, Callback callback) = 0; protected: - virtual ~CallbackHandler() = default; + virtual ~CallbackHandler(); }; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/DescriptorSetOffsetArray.h b/package/ios/libs/filament/include/backend/DescriptorSetOffsetArray.h new file mode 100644 index 00000000..d7cfb763 --- /dev/null +++ b/package/ios/libs/filament/include/backend/DescriptorSetOffsetArray.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H +#define TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H + +#include + +#include +#include + +#include +#include + + +namespace utils::io { +class ostream; +} // namespace utils::io + +namespace filament::backend { + +void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept; + +class DescriptorSetOffsetArray { +public: + using value_type = uint32_t; + using reference = value_type&; + using const_reference = value_type const&; + using size_type = uint32_t; + using difference_type = int32_t; + using pointer = value_type*; + using const_pointer = value_type const*; + using iterator = pointer; + using const_iterator = const_pointer; + + DescriptorSetOffsetArray() noexcept = default; + + ~DescriptorSetOffsetArray() noexcept = default; + + DescriptorSetOffsetArray(size_type size, DriverApi& driver) noexcept { + mOffsets = (value_type *)allocateFromCommandStream(driver, + size * sizeof(value_type), alignof(value_type)); + std::uninitialized_fill_n(mOffsets, size, 0); + } + + DescriptorSetOffsetArray(std::initializer_list list, DriverApi& driver) noexcept { + mOffsets = (value_type *)allocateFromCommandStream(driver, + list.size() * sizeof(value_type), alignof(value_type)); + std::uninitialized_copy(list.begin(), list.end(), mOffsets); + } + + DescriptorSetOffsetArray(DescriptorSetOffsetArray const&) = delete; + DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray const&) = delete; + + DescriptorSetOffsetArray(DescriptorSetOffsetArray&& rhs) noexcept + : mOffsets(rhs.mOffsets) { + rhs.mOffsets = nullptr; + } + + DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray&& rhs) noexcept { + if (this != &rhs) { + mOffsets = rhs.mOffsets; + rhs.mOffsets = nullptr; + } + return *this; + } + + bool empty() const noexcept { return mOffsets == nullptr; } + + value_type* data() noexcept { return mOffsets; } + const value_type* data() const noexcept { return mOffsets; } + + + reference operator[](size_type n) noexcept { + return *(data() + n); + } + + const_reference operator[](size_type n) const noexcept { + return *(data() + n); + } + + void clear() noexcept { + mOffsets = nullptr; + } + +private: + value_type *mOffsets = nullptr; +}; + +} // namespace filament::backend + +#if !defined(NDEBUG) +utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::DescriptorSetOffsetArray& rhs); +#endif + +#endif //TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H diff --git a/package/ios/libs/filament/include/backend/DriverEnums.h b/package/ios/libs/filament/include/backend/DriverEnums.h index d69a9991..3b1ae238 100644 --- a/package/ios/libs/filament/include/backend/DriverEnums.h +++ b/package/ios/libs/filament/include/backend/DriverEnums.h @@ -19,24 +19,32 @@ #ifndef TNT_FILAMENT_BACKEND_DRIVERENUMS_H #define TNT_FILAMENT_BACKEND_DRIVERENUMS_H -#include #include // Because we define ERROR in the FenceStatus enum. #include #include +#include +#include +#include #include -#include +#include +#include #include -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers +#include +#include +#include +#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + /** * Types and enums used by filament's driver. * @@ -93,10 +101,22 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON */ static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40; +/** + * Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the + * given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is + * true. + * + * This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it + * because the swapchain MSAA settings must be configured before window creation. + */ +static constexpr uint64_t SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES = 0x80; + static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3. static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects. static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr size_t MAX_DESCRIPTOR_SET_COUNT = 4; // This is guaranteed by Vulkan. +static constexpr size_t MAX_DESCRIPTOR_COUNT = 64; // per set static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte // of push constant (we assume 4-byte @@ -109,8 +129,8 @@ static constexpr struct { const size_t MAX_FRAGMENT_SAMPLER_COUNT; } FEATURE_LEVEL_CAPS[4] = { { 0, 0 }, // do not use - { 16, 16 }, // guaranteed by OpenGL ES, Vulkan and Metal - { 16, 16 }, // guaranteed by OpenGL ES, Vulkan and Metal + { 16, 16 }, // guaranteed by OpenGL ES, Vulkan, Metal And WebGPU + { 16, 16 }, // guaranteed by OpenGL ES, Vulkan, Metal And WebGPU { 31, 31 }, // guaranteed by Metal }; @@ -121,6 +141,10 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT, static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES. static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr uint8_t EXTERNAL_SAMPLER_DATA_INDEX_UNUSED = + uint8_t(-1);// Case where the descriptor set binding isnt using any external sampler state + // and therefore doesn't have a valid entry. + /** * Defines the backend's feature levels. */ @@ -139,7 +163,8 @@ enum class Backend : uint8_t { OPENGL = 1, //!< Selects the OpenGL/ES driver (default on Android) VULKAN = 2, //!< Selects the Vulkan driver if the platform supports it (default on Linux/Windows) METAL = 3, //!< Selects the Metal driver if the platform supports it (default on MacOS/iOS). - NOOP = 4, //!< Selects the no-op driver for testing purposes. + WEBGPU = 4, //!< Selects the Webgpu driver if the platform supports webgpu. + NOOP = 5, //!< Selects the no-op driver for testing purposes. }; enum class TimerQueryResult : int8_t { @@ -148,7 +173,7 @@ enum class TimerQueryResult : int8_t { AVAILABLE = 1, // result is available }; -static constexpr const char* backendToString(Backend backend) { +constexpr std::string_view to_string(Backend const backend) noexcept { switch (backend) { case Backend::NOOP: return "Noop"; @@ -158,9 +183,12 @@ static constexpr const char* backendToString(Backend backend) { return "Vulkan"; case Backend::METAL: return "Metal"; - default: - return "Unknown"; + case Backend::WEBGPU: + return "WebGPU"; + case Backend::DEFAULT: + return "Default"; } + return "Unknown"; } /** @@ -169,14 +197,16 @@ static constexpr const char* backendToString(Backend backend) { * - The Metal backend can prefer precompiled Metal libraries, while falling back to MSL. */ enum class ShaderLanguage { + UNSPECIFIED = -1, ESSL1 = 0, ESSL3 = 1, SPIRV = 2, MSL = 3, METAL_LIBRARY = 4, + WGSL = 5, }; -static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) { +constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) noexcept { switch (shaderLanguage) { case ShaderLanguage::ESSL1: return "ESSL 1.0"; @@ -188,9 +218,310 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag return "MSL"; case ShaderLanguage::METAL_LIBRARY: return "Metal precompiled library"; + case ShaderLanguage::WGSL: + return "WGSL"; + case ShaderLanguage::UNSPECIFIED: + return "Unspecified"; + } + return "UNKNOWN"; +} + +enum class ShaderStage : uint8_t { + VERTEX = 0, + FRAGMENT = 1, + COMPUTE = 2 +}; + +static constexpr size_t PIPELINE_STAGE_COUNT = 2; +enum class ShaderStageFlags : uint8_t { + NONE = 0, + VERTEX = 0x1, + FRAGMENT = 0x2, + COMPUTE = 0x4, + ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE +}; + +constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept { + switch (type) { + case ShaderStage::VERTEX: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX)); + case ShaderStage::FRAGMENT: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT)); + case ShaderStage::COMPUTE: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE)); + } +} + +enum class TextureType : uint8_t { + FLOAT, + INT, + UINT, + DEPTH, + STENCIL, + DEPTH_STENCIL +}; + +constexpr std::string_view to_string(TextureType type) noexcept { + switch (type) { + case TextureType::FLOAT: return "FLOAT"; + case TextureType::INT: return "INT"; + case TextureType::UINT: return "UINT"; + case TextureType::DEPTH: return "DEPTH"; + case TextureType::STENCIL: return "STENCIL"; + case TextureType::DEPTH_STENCIL: return "DEPTH_STENCIL"; + } + return "UNKNOWN"; +} + + enum class DescriptorType : uint8_t { + SAMPLER_2D_FLOAT, + SAMPLER_2D_INT, + SAMPLER_2D_UINT, + SAMPLER_2D_DEPTH, + + SAMPLER_2D_ARRAY_FLOAT, + SAMPLER_2D_ARRAY_INT, + SAMPLER_2D_ARRAY_UINT, + SAMPLER_2D_ARRAY_DEPTH, + + SAMPLER_CUBE_FLOAT, + SAMPLER_CUBE_INT, + SAMPLER_CUBE_UINT, + SAMPLER_CUBE_DEPTH, + + SAMPLER_CUBE_ARRAY_FLOAT, + SAMPLER_CUBE_ARRAY_INT, + SAMPLER_CUBE_ARRAY_UINT, + SAMPLER_CUBE_ARRAY_DEPTH, + + SAMPLER_3D_FLOAT, + SAMPLER_3D_INT, + SAMPLER_3D_UINT, + + SAMPLER_2D_MS_FLOAT, + SAMPLER_2D_MS_INT, + SAMPLER_2D_MS_UINT, + + SAMPLER_2D_MS_ARRAY_FLOAT, + SAMPLER_2D_MS_ARRAY_INT, + SAMPLER_2D_MS_ARRAY_UINT, + + SAMPLER_EXTERNAL, + UNIFORM_BUFFER, + SHADER_STORAGE_BUFFER, + INPUT_ATTACHMENT, + }; + +constexpr bool isDepthDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_DEPTH: + case DescriptorType::SAMPLER_2D_ARRAY_DEPTH: + case DescriptorType::SAMPLER_CUBE_DEPTH: + case DescriptorType::SAMPLER_CUBE_ARRAY_DEPTH: + return true; + default: ; + } + return false; +} + +constexpr bool isFloatDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_FLOAT: + case DescriptorType::SAMPLER_2D_ARRAY_FLOAT: + case DescriptorType::SAMPLER_CUBE_FLOAT: + case DescriptorType::SAMPLER_CUBE_ARRAY_FLOAT: + case DescriptorType::SAMPLER_3D_FLOAT: + case DescriptorType::SAMPLER_2D_MS_FLOAT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT: + return true; + default: ; + } + return false; +} + +constexpr bool isIntDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_INT: + case DescriptorType::SAMPLER_2D_ARRAY_INT: + case DescriptorType::SAMPLER_CUBE_INT: + case DescriptorType::SAMPLER_CUBE_ARRAY_INT: + case DescriptorType::SAMPLER_3D_INT: + case DescriptorType::SAMPLER_2D_MS_INT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_INT: + return true; + default: ; + } + return false; +} + +constexpr bool isUnsignedIntDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_UINT: + case DescriptorType::SAMPLER_2D_ARRAY_UINT: + case DescriptorType::SAMPLER_CUBE_UINT: + case DescriptorType::SAMPLER_CUBE_ARRAY_UINT: + case DescriptorType::SAMPLER_3D_UINT: + case DescriptorType::SAMPLER_2D_MS_UINT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool is3dTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_3D_FLOAT: + case DescriptorType::SAMPLER_3D_INT: + case DescriptorType::SAMPLER_3D_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool is2dTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_FLOAT: + case DescriptorType::SAMPLER_2D_INT: + case DescriptorType::SAMPLER_2D_UINT: + case DescriptorType::SAMPLER_2D_DEPTH: + case DescriptorType::SAMPLER_2D_MS_FLOAT: + case DescriptorType::SAMPLER_2D_MS_INT: + case DescriptorType::SAMPLER_2D_MS_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool is2dArrayTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_ARRAY_FLOAT: + case DescriptorType::SAMPLER_2D_ARRAY_INT: + case DescriptorType::SAMPLER_2D_ARRAY_UINT: + case DescriptorType::SAMPLER_2D_ARRAY_DEPTH: + case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_INT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: + return true; + default: ; + } + return false; +} + +constexpr bool isCubeTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_CUBE_FLOAT: + case DescriptorType::SAMPLER_CUBE_INT: + case DescriptorType::SAMPLER_CUBE_UINT: + case DescriptorType::SAMPLER_CUBE_DEPTH: + return true; + default: ; + } + return false; +} + +constexpr bool isCubeArrayTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_CUBE_ARRAY_FLOAT: + case DescriptorType::SAMPLER_CUBE_ARRAY_INT: + case DescriptorType::SAMPLER_CUBE_ARRAY_UINT: + case DescriptorType::SAMPLER_CUBE_ARRAY_DEPTH: + return true; + default: ; } + return false; } +constexpr bool isMultiSampledTypeDescriptor(DescriptorType const type) noexcept { + switch (type) { + case DescriptorType::SAMPLER_2D_MS_FLOAT: + case DescriptorType::SAMPLER_2D_MS_INT: + case DescriptorType::SAMPLER_2D_MS_UINT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_INT: + case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: + return true; + default: ; + } + return false; +} + +constexpr std::string_view to_string(DescriptorType type) noexcept { + #define DESCRIPTOR_TYPE_CASE(TYPE) case DescriptorType::TYPE: return #TYPE; + switch (type) { + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_ARRAY_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_CUBE_ARRAY_DEPTH) + DESCRIPTOR_TYPE_CASE(SAMPLER_3D_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_3D_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_3D_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_FLOAT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_INT) + DESCRIPTOR_TYPE_CASE(SAMPLER_2D_MS_ARRAY_UINT) + DESCRIPTOR_TYPE_CASE(SAMPLER_EXTERNAL) + DESCRIPTOR_TYPE_CASE(UNIFORM_BUFFER) + DESCRIPTOR_TYPE_CASE(SHADER_STORAGE_BUFFER) + DESCRIPTOR_TYPE_CASE(INPUT_ATTACHMENT) + } + return "UNKNOWN"; + #undef DESCRIPTOR_TYPE_CASE +} + +enum class DescriptorFlags : uint8_t { + NONE = 0x00, + + // Indicate a UNIFORM_BUFFER will have dynamic offsets. + DYNAMIC_OFFSET = 0x01, + + // To indicate a texture/sampler type should be unfiltered. + UNFILTERABLE = 0x02, +}; + +using descriptor_set_t = uint8_t; + +using descriptor_binding_t = uint8_t; + +struct DescriptorSetLayoutDescriptor { + static bool isSampler(DescriptorType type) noexcept { + return int(type) <= int(DescriptorType::SAMPLER_EXTERNAL); + } + static bool isBuffer(DescriptorType type) noexcept { + return type == DescriptorType::UNIFORM_BUFFER || + type == DescriptorType::SHADER_STORAGE_BUFFER; + } + DescriptorType type; + ShaderStageFlags stageFlags; + descriptor_binding_t binding; + DescriptorFlags flags = DescriptorFlags::NONE; + uint16_t count = 0; + + friend bool operator==(DescriptorSetLayoutDescriptor const& lhs, + DescriptorSetLayoutDescriptor const& rhs) noexcept { + return lhs.type == rhs.type && + lhs.flags == rhs.flags && + lhs.count == rhs.count && + lhs.stageFlags == rhs.stageFlags; + } +}; + /** * Bitmask for selecting render buffers */ @@ -213,7 +544,7 @@ enum class TargetBufferFlags : uint32_t { ALL = COLOR_ALL | DEPTH | STENCIL //!< Color, depth and stencil buffer selected. }; -inline constexpr TargetBufferFlags getTargetBufferFlagsAt(size_t index) noexcept { +constexpr TargetBufferFlags getTargetBufferFlagsAt(size_t index) noexcept { if (index == 0u) return TargetBufferFlags::COLOR0; if (index == 1u) return TargetBufferFlags::COLOR1; if (index == 2u) return TargetBufferFlags::COLOR2; @@ -228,12 +559,21 @@ inline constexpr TargetBufferFlags getTargetBufferFlagsAt(size_t index) noexcept } /** - * Frequency at which a buffer is expected to be modified and used. This is used as an hint - * for the driver to make better decisions about managing memory internally. + * How the buffer will be used. */ enum class BufferUsage : uint8_t { - STATIC, //!< content modified once, used many times - DYNAMIC, //!< content modified frequently, used many times + STATIC = 0, //!< (legacy) content modified once, used many times + DYNAMIC = 1, //!< (legacy) content modified frequently, used many times + DYNAMIC_BIT = 0x1, //!< buffer can be modified frequently, used many times + SHARED_WRITE_BIT = 0x04, //!< buffer can be memory mapped for write operations +}; + +/** + * How the buffer will be mapped. + */ +enum class MapBufferAccessFlags : uint8_t { + WRITE_BIT = 0x2, //!< buffer is mapped from writing + INVALIDATE_RANGE_BIT = 0x4, //!< the mapped range content is lost }; /** @@ -249,8 +589,19 @@ struct Viewport { int32_t right() const noexcept { return left + int32_t(width); } //! get the top coordinate in window space of the viewport int32_t top() const noexcept { return bottom + int32_t(height); } -}; + friend bool operator==(Viewport const& lhs, Viewport const& rhs) noexcept { + // clang can do this branchless with xor/or + return lhs.left == rhs.left && lhs.bottom == rhs.bottom && + lhs.width == rhs.width && lhs.height == rhs.height; + } + + friend bool operator!=(Viewport const& lhs, Viewport const& rhs) noexcept { + // clang is being dumb and uses branches + return bool(((lhs.left ^ rhs.left) | (lhs.bottom ^ rhs.bottom)) | + ((lhs.width ^ rhs.width) | (lhs.height ^ rhs.height))); + } +}; /** * Specifies the mapping of the near and far clipping plane to window coordinates. @@ -270,15 +621,6 @@ enum class FenceStatus : int8_t { TIMEOUT_EXPIRED = 1, //!< wait()'s timeout expired. The Fence condition is not satisfied. }; -/** - * Status codes for sync objects - */ -enum class SyncStatus : int8_t { - ERROR = -1, //!< An error occurred. The Sync is not signaled. - SIGNALED = 0, //!< The Sync is signaled. - NOT_SIGNALED = 1, //!< The Sync is not signaled yet -}; - static constexpr uint64_t FENCE_WAIT_FOR_EVER = uint64_t(-1); /** @@ -297,6 +639,15 @@ enum class ShaderModel : uint8_t { }; static constexpr size_t SHADER_MODEL_COUNT = 2; +constexpr std::string_view to_string(ShaderModel model) noexcept { + switch (model) { + case ShaderModel::MOBILE: + return "mobile"; + case ShaderModel::DESKTOP: + return "desktop"; + } +} + /** * Primitive types */ @@ -309,6 +660,18 @@ enum class PrimitiveType : uint8_t { TRIANGLE_STRIP = 5 //!< triangle strip }; +[[nodiscard]] constexpr bool isStripPrimitiveType(const PrimitiveType type) { + switch (type) { + case PrimitiveType::POINTS: + case PrimitiveType::LINES: + case PrimitiveType::TRIANGLES: + return false; + case PrimitiveType::LINE_STRIP: + case PrimitiveType::TRIANGLE_STRIP: + return true; + } +} + /** * Supported uniform types */ @@ -350,11 +713,29 @@ enum class Precision : uint8_t { DEFAULT }; +union ConstantValue { + int32_t i; + float f; + bool b; +}; + /** * Shader compiler priority queue + * + * On platforms which support parallel shader compilation, compilation requests will be processed in + * order of priority, then insertion order. See Material::compile(). */ enum class CompilerPriorityQueue : uint8_t { + /** We need this program NOW. + * + * When passed as an argument to Material::compile(), if the platform doesn't support parallel + * compilation, but does support amortized shader compilation, the given shader program will be + * synchronously compiled. + */ + CRITICAL, + /** We will need this program soon. */ HIGH, + /** We will need this program eventually. */ LOW }; @@ -368,6 +749,24 @@ enum class SamplerType : uint8_t { SAMPLER_CUBEMAP_ARRAY, //!< Cube map array texture (feature level 2) }; +constexpr std::string_view to_string(SamplerType const type) noexcept { + switch (type) { + case SamplerType::SAMPLER_2D: + return "SAMPLER_2D"; + case SamplerType::SAMPLER_2D_ARRAY: + return "SAMPLER_2D_ARRAY"; + case SamplerType::SAMPLER_CUBEMAP: + return "SAMPLER_CUBEMAP"; + case SamplerType::SAMPLER_EXTERNAL: + return "SAMPLER_EXTERNAL"; + case SamplerType::SAMPLER_3D: + return "SAMPLER_3D"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "SAMPLER_CUBEMAP_ARRAY"; + } + return "Unknown"; +} + //! Subpass type enum class SubpassType : uint8_t { SUBPASS_INPUT @@ -381,6 +780,20 @@ enum class SamplerFormat : uint8_t { SHADOW = 3 //!< shadow sampler (PCF) }; +constexpr std::string_view to_string(SamplerFormat const format) noexcept { + switch (format) { + case SamplerFormat::INT: + return "INT"; + case SamplerFormat::UINT: + return "UINT"; + case SamplerFormat::FLOAT: + return "FLOAT"; + case SamplerFormat::SHADOW: + return "SHADOW"; + } + return "Unknown"; +} + /** * Supported element types */ @@ -420,6 +833,15 @@ enum class BufferObjectBinding : uint8_t { SHADER_STORAGE }; +constexpr std::string_view to_string(BufferObjectBinding type) noexcept { + switch (type) { + case BufferObjectBinding::VERTEX: return "VERTEX"; + case BufferObjectBinding::UNIFORM: return "UNIFORM"; + case BufferObjectBinding::SHADER_STORAGE: return "SHADER_STORAGE"; + } + return "UNKNOWN"; +} + //! Face culling Mode enum class CullingMode : uint8_t { NONE, //!< No culling, front and back faces are visible @@ -681,6 +1103,8 @@ enum class TextureFormat : uint16_t { SRGB_ALPHA_BPTC_UNORM, // BC7 sRGB }; +TextureType getTextureType(TextureFormat format) noexcept; + //! Bitmask describing the intended Texture Usage enum class TextureUsage : uint16_t { NONE = 0x0000, @@ -693,7 +1117,9 @@ enum class TextureUsage : uint16_t { BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() PROTECTED = 0x0100, //!< Texture can be used for protected content - DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage + GEN_MIPMAPPABLE = 0x0200, //!< Texture can be used with generateMipmaps() + DEFAULT = UPLOADABLE | SAMPLEABLE, //!< Default texture usage + ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments }; //! Texture swizzle @@ -707,7 +1133,7 @@ enum class TextureSwizzle : uint8_t { }; //! returns whether this format a depth format -static constexpr bool isDepthFormat(TextureFormat format) noexcept { +constexpr bool isDepthFormat(TextureFormat format) noexcept { switch (format) { case TextureFormat::DEPTH32F: case TextureFormat::DEPTH24: @@ -720,7 +1146,7 @@ static constexpr bool isDepthFormat(TextureFormat format) noexcept { } } -static constexpr bool isStencilFormat(TextureFormat format) noexcept { +constexpr bool isStencilFormat(TextureFormat format) noexcept { switch (format) { case TextureFormat::STENCIL8: case TextureFormat::DEPTH24_STENCIL8: @@ -731,7 +1157,34 @@ static constexpr bool isStencilFormat(TextureFormat format) noexcept { } } -static constexpr bool isUnsignedIntFormat(TextureFormat format) { +constexpr bool isColorFormat(TextureFormat format) noexcept { + switch (format) { + // Standard color formats + case TextureFormat::R8: + case TextureFormat::RG8: + case TextureFormat::RGBA8: + case TextureFormat::R16F: + case TextureFormat::RG16F: + case TextureFormat::RGBA16F: + case TextureFormat::R32F: + case TextureFormat::RG32F: + case TextureFormat::RGBA32F: + case TextureFormat::RGB10_A2: + case TextureFormat::R11F_G11F_B10F: + case TextureFormat::SRGB8: + case TextureFormat::SRGB8_A8: + case TextureFormat::RGB8: + case TextureFormat::RGB565: + case TextureFormat::RGB5_A1: + case TextureFormat::RGBA4: + return true; + default: + break; + } + return false; +} + +constexpr bool isUnsignedIntFormat(TextureFormat format) { switch (format) { case TextureFormat::R8UI: case TextureFormat::R16UI: @@ -752,7 +1205,7 @@ static constexpr bool isUnsignedIntFormat(TextureFormat format) { } } -static constexpr bool isSignedIntFormat(TextureFormat format) { +constexpr bool isSignedIntFormat(TextureFormat format) { switch (format) { case TextureFormat::R8I: case TextureFormat::R16I: @@ -774,35 +1227,35 @@ static constexpr bool isSignedIntFormat(TextureFormat format) { } //! returns whether this format is a compressed format -static constexpr bool isCompressedFormat(TextureFormat format) noexcept { +constexpr bool isCompressedFormat(TextureFormat format) noexcept { return format >= TextureFormat::EAC_R11; } //! returns whether this format is an ETC2 compressed format -static constexpr bool isETC2Compression(TextureFormat format) noexcept { +constexpr bool isETC2Compression(TextureFormat format) noexcept { return format >= TextureFormat::EAC_R11 && format <= TextureFormat::ETC2_EAC_SRGBA8; } //! returns whether this format is an S3TC compressed format -static constexpr bool isS3TCCompression(TextureFormat format) noexcept { +constexpr bool isS3TCCompression(TextureFormat format) noexcept { return format >= TextureFormat::DXT1_RGB && format <= TextureFormat::DXT5_SRGBA; } -static constexpr bool isS3TCSRGBCompression(TextureFormat format) noexcept { +constexpr bool isS3TCSRGBCompression(TextureFormat format) noexcept { return format >= TextureFormat::DXT1_SRGB && format <= TextureFormat::DXT5_SRGBA; } //! returns whether this format is an RGTC compressed format -static constexpr bool isRGTCCompression(TextureFormat format) noexcept { +constexpr bool isRGTCCompression(TextureFormat format) noexcept { return format >= TextureFormat::RED_RGTC1 && format <= TextureFormat::SIGNED_RED_GREEN_RGTC2; } //! returns whether this format is an BPTC compressed format -static constexpr bool isBPTCCompression(TextureFormat format) noexcept { +constexpr bool isBPTCCompression(TextureFormat format) noexcept { return format >= TextureFormat::RGB_BPTC_SIGNED_FLOAT && format <= TextureFormat::SRGB_ALPHA_BPTC_UNORM; } -static constexpr bool isASTCCompression(TextureFormat format) noexcept { +constexpr bool isASTCCompression(TextureFormat format) noexcept { return format >= TextureFormat::RGBA_ASTC_4x4 && format <= TextureFormat::SRGB8_ALPHA8_ASTC_12x12; } @@ -863,7 +1316,7 @@ enum class SamplerCompareFunc : uint8_t { }; //! Sampler parameters -struct SamplerParams { // NOLINT +struct SamplerParams { // NOLINT SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST) SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST) SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE) @@ -887,6 +1340,9 @@ struct SamplerParams { // NOLINT struct EqualTo { bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept { + assert_invariant(lhs.padding0 == 0); + assert_invariant(lhs.padding1 == 0); + assert_invariant(lhs.padding2 == 0); auto* pLhs = reinterpret_cast(reinterpret_cast(&lhs)); auto* pRhs = reinterpret_cast(reinterpret_cast(&rhs)); return *pLhs == *pRhs; @@ -895,17 +1351,31 @@ struct SamplerParams { // NOLINT struct LessThan { bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept { + assert_invariant(lhs.padding0 == 0); + assert_invariant(lhs.padding1 == 0); + assert_invariant(lhs.padding2 == 0); auto* pLhs = reinterpret_cast(reinterpret_cast(&lhs)); auto* pRhs = reinterpret_cast(reinterpret_cast(&rhs)); - return *pLhs == *pRhs; + return *pLhs < *pRhs; } }; + bool isFiltered() const noexcept { + return filterMag != SamplerMagFilter::NEAREST || filterMin != SamplerMinFilter::NEAREST; + } + private: - friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept { - return SamplerParams::LessThan{}(lhs, rhs); + friend bool operator == (SamplerParams lhs, SamplerParams rhs) noexcept { + return EqualTo{}(lhs, rhs); + } + friend bool operator != (SamplerParams lhs, SamplerParams rhs) noexcept { + return !EqualTo{}(lhs, rhs); + } + friend bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept { + return LessThan{}(lhs, rhs); } }; + static_assert(sizeof(SamplerParams) == 4); // The limitation to 64-bits max comes from how we store a SamplerParams in our JNI code @@ -913,6 +1383,11 @@ static_assert(sizeof(SamplerParams) == 4); static_assert(sizeof(SamplerParams) <= sizeof(uint64_t), "SamplerParams must be no more than 64 bits"); +struct DescriptorSetLayout { + std::variant label; + utils::FixedCapacityVector descriptors; +}; + //! blending equation function enum class BlendEquation : uint8_t { ADD, //!< the fragment is added to the color buffer @@ -1058,7 +1533,7 @@ struct RasterState { bool inverseFrontFaces : 1; // 31 //! padding, must be 0 - uint8_t padding : 1; // 32 + bool depthClamp : 1; // 32 }; uint32_t u = 0; }; @@ -1069,32 +1544,6 @@ struct RasterState { * \privatesection */ -enum class ShaderStage : uint8_t { - VERTEX = 0, - FRAGMENT = 1, - COMPUTE = 2 -}; - -static constexpr size_t PIPELINE_STAGE_COUNT = 2; -enum class ShaderStageFlags : uint8_t { - NONE = 0, - VERTEX = 0x1, - FRAGMENT = 0x2, - COMPUTE = 0x4, - ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE -}; - -static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept { - switch (type) { - case ShaderStage::VERTEX: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX)); - case ShaderStage::FRAGMENT: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT)); - case ShaderStage::COMPUTE: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE)); - } -} - /** * Selects which buffers to clear at the beginning of the render pass, as well as which buffers * can be discarded at the beginning and end of the render pass. @@ -1232,7 +1681,7 @@ static_assert(sizeof(StencilState::StencilOperations) == 5u, static_assert(sizeof(StencilState) == 12u, "StencilState size not what was intended"); -using FrameScheduledCallback = utils::Invocable; +using FrameScheduledCallback = utils::Invocable; enum class Workaround : uint16_t { // The EASU pass must split because shader compiler flattens early-exit branch @@ -1243,15 +1692,34 @@ enum class Workaround : uint16_t { // for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu ADRENO_UNIFORM_ARRAY_CRASH, // Workaround a Metal pipeline compilation error with the message: - // "Could not statically determine the target of a texture". See light_indirect.fs - A8X_STATIC_TEXTURE_TARGET_ERROR, + // "Could not statically determine the target of a texture". See surface_light_indirect.fs + METAL_STATIC_TEXTURE_TARGET_ERROR, // Adreno drivers sometimes aren't able to blit into a layer of a texture array. DISABLE_BLIT_INTO_TEXTURE_ARRAY, // Multiple workarounds needed for PowerVR GPUs POWER_VR_SHADER_WORKAROUNDS, + // Some browsers, such as Firefox on Mac, struggle with slow shader compile/link times when + // creating programs for the default material, leading to startup stutters. This workaround + // prevents these stutters by not precaching depth variants of the default material for those + // particular browsers. + DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL, + // Emulate an sRGB swapchain in shader code. + EMULATE_SRGB_SWAPCHAIN, }; -using StereoscopicType = backend::Platform::StereoscopicType; +using StereoscopicType = Platform::StereoscopicType; + +using FrameTimestamps = Platform::FrameTimestamps; + +using CompositorTiming = Platform::CompositorTiming; + +using AsynchronousMode = Platform::AsynchronousMode; + +using AsyncCallId = uint32_t; + +static constexpr AsyncCallId InvalidAsyncCallId = std::numeric_limits::max(); + +using AsynchronousMode = Platform::AsynchronousMode; } // namespace filament::backend @@ -1259,10 +1727,17 @@ template<> struct utils::EnableBitMaskOperators struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; template<> struct utils::EnableBitMaskOperators : public std::true_type {}; template<> struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; + template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> struct utils::EnableIntegerOperators @@ -1291,12 +1766,16 @@ utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::Textu utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::TextureUsage usage); utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::BufferObjectBinding binding); utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::TextureSwizzle swizzle); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::ShaderStage shaderStage); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::ShaderStageFlags stageFlags); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::CompilerPriorityQueue compilerPriorityQueue); +utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::PushConstantVariant pushConstantVariant); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::AttributeArray& type); +utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::DescriptorSetLayout& dsl); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::PolygonOffset& po); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::RasterState& rs); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::RenderPassParams& b); utils::io::ostream& operator<<(utils::io::ostream& out, const filament::backend::Viewport& v); -utils::io::ostream& operator<<(utils::io::ostream& out, filament::backend::ShaderStageFlags stageFlags); #endif #endif // TNT_FILAMENT_BACKEND_DRIVERENUMS_H diff --git a/package/ios/libs/filament/include/backend/Handle.h b/package/ios/libs/filament/include/backend/Handle.h index c54e9609..c9b123c0 100644 --- a/package/ios/libs/filament/include/backend/Handle.h +++ b/package/ios/libs/filament/include/backend/Handle.h @@ -17,15 +17,17 @@ #ifndef TNT_FILAMENT_BACKEND_HANDLE_H #define TNT_FILAMENT_BACKEND_HANDLE_H -#if !defined(NDEBUG) -#include -#endif #include #include // FIXME: STL headers are not allowed in public headers +#include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { struct HwBufferObject; @@ -34,13 +36,16 @@ struct HwIndexBuffer; struct HwProgram; struct HwRenderPrimitive; struct HwRenderTarget; -struct HwSamplerGroup; struct HwStream; struct HwSwapChain; +struct HwSync; struct HwTexture; struct HwTimerQuery; struct HwVertexBufferInfo; struct HwVertexBuffer; +struct HwDescriptorSetLayout; +struct HwDescriptorSet; +struct HwMemoryMappedBuffer; /* * A handle to a backend resource. HandleBase is for internal use only. @@ -104,8 +109,18 @@ struct Handle : public HandleBase { Handle(Handle const& rhs) noexcept = default; Handle(Handle&& rhs) noexcept = default; - Handle& operator=(Handle const& rhs) noexcept = default; - Handle& operator=(Handle&& rhs) noexcept = default; + // Explicitly redefine copy/move assignment operators rather than just using default here. + // Because it doesn't make a call to the parent's method automatically during the std::move + // function call(https://en.cppreference.com/w/cpp/algorithm/move) in certain compilers like + // NDK 25.1.8937393 and below (see b/371980551) + Handle& operator=(Handle const& rhs) noexcept { + HandleBase::operator=(rhs); + return *this; + } + Handle& operator=(Handle&& rhs) noexcept { + HandleBase::operator=(std::move(rhs)); + return *this; + } explicit Handle(HandleId id) noexcept : HandleBase(id) { } @@ -118,7 +133,7 @@ struct Handle : public HandleBase { bool operator>=(const Handle& rhs) const noexcept { return getId() >= rhs.getId(); } // type-safe Handle cast - template::value> > + template> > Handle(Handle const& base) noexcept : HandleBase(base) { } // NOLINT(hicpp-explicit-conversions,google-explicit-constructor) private: @@ -130,19 +145,22 @@ struct Handle : public HandleBase { // Types used by the command stream // (we use this renaming because the macro-system doesn't deal well with "<" and ">") -using BufferObjectHandle = Handle; -using FenceHandle = Handle; -using IndexBufferHandle = Handle; -using ProgramHandle = Handle; -using RenderPrimitiveHandle = Handle; -using RenderTargetHandle = Handle; -using SamplerGroupHandle = Handle; -using StreamHandle = Handle; -using SwapChainHandle = Handle; -using TextureHandle = Handle; -using TimerQueryHandle = Handle; -using VertexBufferHandle = Handle; -using VertexBufferInfoHandle = Handle; +using BufferObjectHandle = Handle; +using FenceHandle = Handle; +using IndexBufferHandle = Handle; +using ProgramHandle = Handle; +using RenderPrimitiveHandle = Handle; +using RenderTargetHandle = Handle; +using StreamHandle = Handle; +using SwapChainHandle = Handle; +using SyncHandle = Handle; +using TextureHandle = Handle; +using TimerQueryHandle = Handle; +using VertexBufferHandle = Handle; +using VertexBufferInfoHandle = Handle; +using DescriptorSetLayoutHandle = Handle; +using DescriptorSetHandle = Handle; +using MemoryMappedBufferHandle = Handle; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/PipelineState.h b/package/ios/libs/filament/include/backend/PipelineState.h index 220d04bb..106e579e 100644 --- a/package/ios/libs/filament/include/backend/PipelineState.h +++ b/package/ios/libs/filament/include/backend/PipelineState.h @@ -20,17 +20,27 @@ #include #include -#include +#include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { //! \privatesection +struct PipelineLayout { + using SetLayout = std::array, MAX_DESCRIPTOR_SET_COUNT>; + SetLayout setLayout; // 16 +}; + struct PipelineState { Handle program; // 4 Handle vertexBufferInfo; // 4 + PipelineLayout pipelineLayout; // 16 RasterState rasterState; // 4 StencilState stencilState; // 12 PolygonOffset polygonOffset; // 8 diff --git a/package/ios/libs/filament/include/backend/PixelBufferDescriptor.h b/package/ios/libs/filament/include/backend/PixelBufferDescriptor.h index c45f344d..93f82c9d 100644 --- a/package/ios/libs/filament/include/backend/PixelBufferDescriptor.h +++ b/package/ios/libs/filament/include/backend/PixelBufferDescriptor.h @@ -24,11 +24,14 @@ #include #include -#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { /** @@ -201,23 +204,15 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { }, new T(std::forward(functor)) }; } - - // -------------------------------------------------------------------------------------------- - /** - * Computes the size in bytes needed to fit an image of given dimensions and format + * Computes the size in bytes for a pixel of given dimensions and format * * @param format Format of the image pixels * @param type Type of the image pixels - * @param stride Stride of a row in pixels - * @param height Height of the image in rows - * @param alignment Alignment in bytes of pixel rows - * @return The buffer size needed to fit this image in bytes + * @return The size of the specified pixel in bytes */ - static constexpr size_t computeDataSize(PixelDataFormat format, PixelDataType type, - size_t stride, size_t height, size_t alignment) noexcept { - assert_invariant(alignment); + static constexpr size_t computePixelSize(PixelDataFormat format, PixelDataType type) noexcept { if (type == PixelDataType::COMPRESSED) { return 0; } @@ -239,7 +234,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { case PixelDataFormat::RGB_INTEGER: n = 3; break; - case PixelDataFormat::UNUSED: // shouldn't happen (used to be rgbm) + case PixelDataFormat::UNUSED:// shouldn't happen (used to be rgbm) case PixelDataFormat::RGBA: case PixelDataFormat::RGBA_INTEGER: n = 4; @@ -248,7 +243,7 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { size_t bpp = n; switch (type) { - case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings + case PixelDataType::COMPRESSED:// Impossible -- to squash the IDE warnings case PixelDataType::UBYTE: case PixelDataType::BYTE: // nothing to do @@ -279,16 +274,35 @@ class UTILS_PUBLIC PixelBufferDescriptor : public BufferDescriptor { bpp = 2; break; } + return bpp; + } + + // -------------------------------------------------------------------------------------------- + + /** + * Computes the size in bytes needed to fit an image of given dimensions and format + * + * @param format Format of the image pixels + * @param type Type of the image pixels + * @param stride Stride of a row in pixels + * @param height Height of the image in rows + * @param alignment Alignment in bytes of pixel rows + * @return The buffer size needed to fit this image in bytes + */ + static constexpr size_t computeDataSize(PixelDataFormat format, PixelDataType type, + size_t stride, size_t height, size_t alignment) noexcept { + assert_invariant(alignment); + size_t bpp = computePixelSize(format, type); size_t const bpr = bpp * stride; size_t const bprAligned = (bpr + (alignment - 1)) & (~alignment + 1); return bprAligned * height; } //! left coordinate in pixels - uint32_t left = 0; + uint32_t left = 0; //! top coordinate in pixels - uint32_t top = 0; + uint32_t top = 0; union { struct { //! stride in pixels diff --git a/package/ios/libs/filament/include/backend/Platform.h b/package/ios/libs/filament/include/backend/Platform.h index 4f73c437..dcbc9928 100644 --- a/package/ios/libs/filament/include/backend/Platform.h +++ b/package/ios/libs/filament/include/backend/Platform.h @@ -19,14 +19,21 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORM_H +#include #include #include +#include #include #include +#include +#include +#include + namespace filament::backend { +class CallbackHandler; class Driver; /** @@ -40,6 +47,163 @@ class UTILS_PUBLIC Platform { struct SwapChain {}; struct Fence {}; struct Stream {}; + struct Sync {}; + + using SyncCallback = void(*)(Sync* UTILS_NONNULL sync, void* UTILS_NULLABLE userData); + + class ExternalImageHandle; + + class ExternalImage { + friend class ExternalImageHandle; + std::atomic_uint32_t mRefCount{0}; + protected: + virtual ~ExternalImage() noexcept; + }; + + class ExternalImageHandle { + ExternalImage* UTILS_NULLABLE mTarget = nullptr; + static void incref(ExternalImage* UTILS_NULLABLE p) noexcept; + static void decref(ExternalImage* UTILS_NULLABLE p) noexcept; + + public: + ExternalImageHandle() noexcept; + ~ExternalImageHandle() noexcept; + explicit ExternalImageHandle(ExternalImage* UTILS_NULLABLE p) noexcept; + ExternalImageHandle(ExternalImageHandle const& rhs) noexcept; + ExternalImageHandle(ExternalImageHandle&& rhs) noexcept; + ExternalImageHandle& operator=(ExternalImageHandle const& rhs) noexcept; + ExternalImageHandle& operator=(ExternalImageHandle&& rhs) noexcept; + + bool operator==(const ExternalImageHandle& rhs) const noexcept { + return mTarget == rhs.mTarget; + } + explicit operator bool() const noexcept { return mTarget != nullptr; } + + ExternalImage* UTILS_NULLABLE get() noexcept { return mTarget; } + ExternalImage const* UTILS_NULLABLE get() const noexcept { return mTarget; } + + ExternalImage* UTILS_NULLABLE operator->() noexcept { return mTarget; } + ExternalImage const* UTILS_NULLABLE operator->() const noexcept { return mTarget; } + + ExternalImage& operator*() noexcept { return *mTarget; } + ExternalImage const& operator*() const noexcept { return *mTarget; } + + void clear() noexcept; + void reset(ExternalImage* UTILS_NULLABLE p) noexcept; + + private: + friend utils::io::ostream& operator<<(utils::io::ostream& out, + ExternalImageHandle const& handle); + }; + + using ExternalImageHandleRef = ExternalImageHandle const&; + + struct CompositorTiming { + /** duration in nanosecond since epoch of std::steady_clock */ + using time_point_ns = int64_t; + /** duration in nanosecond on the std::steady_clock */ + using duration_ns = int64_t; + static constexpr time_point_ns INVALID = -1; //!< value not supported + /** + * The timestamp [ns] since epoch of the next time the compositor will begin composition. + * This is effectively the deadline for when the compositor must receive a newly queued + * frame. + */ + time_point_ns compositeDeadline; + + /** + * The time delta [ns] between subsequent composition events. + */ + duration_ns compositeInterval; + + /** + * The time delta [ns] between the start of composition and the expected present time of + * that composition. This can be used to estimate the latency of the actual present time. + */ + duration_ns compositeToPresentLatency; + + /** + * The timestamp [ns] since epoch of the system's expected presentation time. + * INVALID if not supported. + */ + time_point_ns expectedPresentTime; + + /** + * The timestamp [ns] since epoch of the current frame's start (i.e. vsync) + * INVALID if not supported. + */ + time_point_ns frameTime; + + /** + * The timestamp [ns] since epoch of the current frame's deadline + * INVALID if not supported. + */ + time_point_ns frameTimelineDeadline; + }; + + struct FrameTimestamps { + /** duration in nanosecond since epoch of std::steady_clock */ + using time_point_ns = int64_t; + static constexpr time_point_ns INVALID = -1; //!< value not supported + static constexpr time_point_ns PENDING = -2; //!< value not yet available + + /** + * The time the application requested this frame be presented. + * If the application does not request a presentation time explicitly, + * this will correspond to buffer's queue time. + */ + time_point_ns requestedPresentTime; + + /** + * The time when all the application's rendering to the surface was completed. + */ + time_point_ns acquireTime; + + /** + * The time when the compositor selected this frame as the one to use for the next + * composition. This is the earliest indication that the frame was submitted in time. + */ + time_point_ns latchTime; + + /** + * The first time at which the compositor began preparing composition for this frame. + * Zero if composition was handled by the display and the compositor didn't do any + * rendering. + */ + time_point_ns firstCompositionStartTime; + + /** + * The last time at which the compositor began preparing composition for this frame, for + * frames composited more than once. Zero if composition was handled by the display and the + * compositor didn't do any rendering. + */ + time_point_ns lastCompositionStartTime; + + /** + * The time at which the compositor's rendering work for this frame finished. This will be + * INVALID if composition was handled by the display and the compositor didn't do any + * rendering. + */ + time_point_ns gpuCompositionDoneTime; + + /** + * The time at which this frame started to scan out to the physical display. + */ + time_point_ns displayPresentTime; + + /** + * The time when the buffer became available for reuse as a buffer the client can target + * without blocking. This is generally the point when all read commands of the buffer have + * been submitted, but not necessarily completed. + */ + time_point_ns dequeueReadyTime; + + /** + * The time at which all reads for the purpose of display/composition were completed for + * this frame. + */ + time_point_ns releaseTime; + }; /** * The type of technique for stereoscopic rendering. (Note that the materials used will need to @@ -61,6 +225,58 @@ class UTILS_PUBLIC Platform { MULTIVIEW, }; + /** + * This controls the priority level for GPU work scheduling, which helps prioritize the + * submitted GPU work and enables preemption. + */ + enum class GpuContextPriority : uint8_t { + /** + * Backend default GPU context priority (typically MEDIUM) + */ + DEFAULT, + /** + * For non-interactive, deferrable workloads. This should not interfere with standard + * applications. + */ + LOW, + /** + * The default priority level for standard applications. + */ + MEDIUM, + /** + * For high-priority, latency-sensitive workloads that are more important than standard + * applications. + */ + HIGH, + /** + * The highest priority, intended for system-critical, real-time applications where missing + * deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks). + */ + REALTIME, + }; + + /** + * Defines how asynchronous operations are handled by the engine. + */ + enum class AsynchronousMode : uint8_t { + /** + * Asynchronous operations are disabled. This is the default. + */ + NONE, + + /** + * Attempts to use a dedicated worker thread for asynchronous tasks. If threading is not + * supported by the platform, it automatically falls back to using an amortization strategy. + */ + THREAD_PREFERRED, + + /** + * Uses an amortization strategy, processing a small number of asynchronous tasks during + * each engine update cycle. + */ + AMORTIZATION, + }; + struct DriverConfig { /** * Size of handle arena in bytes. Setting to 0 indicates default value is to be used. @@ -68,24 +284,31 @@ class UTILS_PUBLIC Platform { */ size_t handleArenaSize = 0; - /** - * This number of most-recently destroyed textures will be tracked for use-after-free. - * Throws an exception when a texture is freed but still bound to a SamplerGroup and used in - * a draw call. 0 disables completely. Currently only respected by the Metal backend. - */ - size_t textureUseAfterFreePoolSize = 0; + size_t metalUploadBufferSizeBytes = 512 * 1024; /** * Set to `true` to forcibly disable parallel shader compilation in the backend. - * Currently only honored by the GL and Metal backends. + * Currently only honored by the GL and Metal backends, and the Vulkan backend + * when some experimental features are enabled. */ bool disableParallelShaderCompile = false; + /** + * Set to `true` to forcibly disable amortized shader compilation in the backend. + * Currently only honored by the GL backend. + */ + bool disableAmortizedShaderCompile = true; + /** * Disable backend handles use-after-free checks. */ bool disableHandleUseAfterFreeCheck = false; + /** + * Disable backend handles tags for heap allocated (fallback) handles + */ + bool disableHeapHandleTags = false; + /** * Force GLES2 context if supported, or pretend the context is ES2. Only meaningful on * GLES 3.x backends. @@ -96,6 +319,50 @@ class UTILS_PUBLIC Platform { * Sets the technique for stereoscopic rendering. */ StereoscopicType stereoscopicType = StereoscopicType::NONE; + + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + */ + bool assertNativeWindowIsValid = false; + + /** + * The action to take if a Drawable cannot be acquired. If true, the + * frame is aborted instead of panic. This is only supported for: + * - PlatformMetal + */ + bool metalDisablePanicOnDrawableFailure = false; + + /** + * GPU context priority level. Controls GPU work scheduling and preemption. + * This is only supported for: + * - PlatformEGL + */ + GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT; + + /** + * Enables asynchronous pipeline cache preloading, if supported on this device. + * This is only supported for: + * - VulkanPlatform + * When the following device extensions are available: + * - VK_KHR_dynamic_rendering + * - VK_EXT_vertex_input_dynamic_state + * Should be enabled only for devices where it has been shown this is effective. + */ + bool vulkanEnableAsyncPipelineCachePrewarming = false; + + /** + * Bypass the staging buffer because the device is of Unified Memory Architecture. + * This is only supported for: + * - VulkanPlatform + */ + bool vulkanEnableStagingBufferBypass = false; + + /** + * Asynchronous mode for the engine. Defines how asynchronous operations are handled. + */ + AsynchronousMode asynchronousMode = AsynchronousMode::NONE; }; Platform() noexcept; @@ -116,13 +383,13 @@ class UTILS_PUBLIC Platform { * @param sharedContext an optional shared context. This is not meaningful with all graphic * APIs and platforms. * For EGL platforms, this is an EGLContext. - * + * * @param driverConfig specifies driver initialization parameters * * @return nullptr on failure, or a pointer to the newly created driver. */ - virtual backend::Driver* UTILS_NULLABLE createDriver(void* UTILS_NULLABLE sharedContext, - const DriverConfig& driverConfig) noexcept = 0; + virtual Driver* UTILS_NULLABLE createDriver(void* UTILS_NULLABLE sharedContext, + const DriverConfig& driverConfig) = 0; /** * Processes the platform's event queue when called from its primary event-handling thread. @@ -133,6 +400,65 @@ class UTILS_PUBLIC Platform { */ virtual bool pumpEvents() noexcept; + // -------------------------------------------------------------------------------------------- + // Swapchain timing APIs + + /** + * Whether this platform supports compositor timing querying. + * + * @return true if this Platform supports compositor timings, false otherwise [default] + * @see queryCompositorTiming() + * @see setPresentFrameId() + * @see queryFrameTimestamps() + */ + virtual bool isCompositorTimingSupported() const noexcept; + + /** + * If compositor timing is supported, fills the provided CompositorTiming structure + * with timing information form the compositor the swapchain's native window is using. + * The swapchain'snative window must be valid (i.e. not a headless swapchain). + * @param swapchain to query the compositor timing from + * @return true on success, false otherwise (e.g. if not supported) + * @see isCompositorTimingSupported() + */ + virtual bool queryCompositorTiming(SwapChain const* UTILS_NONNULL swapchain, + CompositorTiming* UTILS_NONNULL outCompositorTiming) const noexcept; + + /** + * Associate a generic frameId which must be monotonically increasing (albeit not strictly) with + * the next frame to be presented on the specified swapchain. + * + * This must be called from the backend thread. + * + * @param swapchain + * @param frameId + * @return true on success, false otherwise + * @see isCompositorTimingSupported() + * @see queryFrameTimestamps() + */ + virtual bool setPresentFrameId(SwapChain const* UTILS_NONNULL swapchain, + uint64_t frameId) noexcept; + + /** + * If compositor timing is supported, fills the provided FrameTimestamps structure + * with timing information of a given frame, identified by the frame id, of the specified + * swapchain. The system only keeps a limited history of frames timings. + * + * This API is thread safe and can be called from any thread. + * + * @param swapchain swapchain to query the timestamps of + * @param frameId frame we're interested it + * @param outFrameTimestamps output structure receiving the timestamps + * @return true if successful, false otherwise + * @see isCompositorTimingSupported() + * @see setPresentFrameId() + */ + virtual bool queryFrameTimestamps(SwapChain const* UTILS_NONNULL swapchain, + uint64_t frameId, FrameTimestamps* UTILS_NONNULL outFrameTimestamps) const noexcept; + + // -------------------------------------------------------------------------------------------- + // Caching APIs + /** * InsertBlobFunc is an Invocable to an application-provided function that a * backend implementation may use to insert a key/value pair into the @@ -225,13 +551,25 @@ class UTILS_PUBLIC Platform { size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize, void* UTILS_NONNULL value, size_t valueSize); - using DebugUpdateStatFunc = utils::Invocable; + // -------------------------------------------------------------------------------------------- + // Debugging APIs + + using DebugUpdateStatFunc = utils::Invocable; /** * Sets the callback function that the backend can use to update backend-specific statistics * to aid with debugging. This callback is guaranteed to be called on the Filament driver * thread. * + * The callback signature is (key, intValue, stringValue). Note that for any given call, + * only one of the value parameters (intValue or stringValue) will be meaningful, depending on + * the specific key. + * + * IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking + * work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should + * be made, which could interfere with Filament's driver state. + * * @param debugUpdateStat an Invocable that updates debug statistics */ void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept; @@ -250,15 +588,32 @@ class UTILS_PUBLIC Platform { * This function is guaranteed to be called only on a single thread, the Filament driver * thread. * - * @param key a null-terminated C-string with the key of the debug statistic - * @param value the updated value of key + * @param key a null-terminated C-string with the key of the debug statistic + * @param intValue the updated integer value of key (the string value passed to the + * callback will be empty) + */ + void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue); + + /** + * To track backend-specific statistics, the backend implementation can call the + * application-provided callback function debugUpdateStatFunc to associate or update a value + * with a given key. It is possible for this function to be called multiple times with the + * same key, in which case newer values should overwrite older values. + * + * This function is guaranteed to be called only on a single thread, the Filament driver + * thread. + * + * @param key a null-terminated C-string with the key of the debug statistic + * @param stringValue the updated string value of key (the integer value passed to the + * callback will be 0) */ - void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value); + void debugUpdateStat(const char* UTILS_NONNULL key, utils::CString stringValue); private: - InsertBlobFunc mInsertBlob; - RetrieveBlobFunc mRetrieveBlob; - DebugUpdateStatFunc mDebugUpdateStat; + std::shared_ptr mInsertBlob; + std::shared_ptr mRetrieveBlob; + std::shared_ptr mDebugUpdateStat; + mutable utils::Mutex mMutex; }; } // namespace filament diff --git a/package/ios/libs/filament/include/backend/PresentCallable.h b/package/ios/libs/filament/include/backend/PresentCallable.h index f37d7704..ea3380c6 100644 --- a/package/ios/libs/filament/include/backend/PresentCallable.h +++ b/package/ios/libs/filament/include/backend/PresentCallable.h @@ -27,12 +27,12 @@ namespace filament::backend { * A PresentCallable is a callable object that, when called, schedules a frame for presentation on * a SwapChain. * - * Typically, Filament's backend is responsible scheduling a frame's presentation. However, there - * are certain cases where the application might want to control when a frame is scheduled for + * Typically, Filament's backend is responsible for scheduling a frame's presentation. However, + * there are certain cases where the application might want to control when a frame is scheduled for * presentation. * * For example, on iOS, UIKit elements can be synchronized to 3D content by scheduling a present - * within a CATransation: + * within a CATransaction: * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * void myFrameScheduledCallback(PresentCallable presentCallable, void* user) { @@ -55,12 +55,13 @@ namespace filament::backend { * } * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * - * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other - * backends ignore the callback (which will never be called) and proceed normally. + * @remark The PresentCallable mechanism for user-controlled presentation is only supported by + * Filament's Metal backend. On other backends, the FrameScheduledCallback is still invoked, but the + * PresentCallable passed to it is a no-op and calling it has no effect. * - * Applications *must* call each PresentCallable they receive. Each PresentCallable represents a - * frame that is waiting to be presented. If an application fails to call a PresentCallable, a - * memory leak could occur. To "cancel" the presentation of a frame, pass false to the + * When using the Metal backend, applications *must* call each PresentCallable they receive. Each + * PresentCallable represents a frame that is waiting to be presented, and failing to call it + * will result in a memory leak. To "cancel" the presentation of a frame, pass false to the * PresentCallable, which will cancel the presentation of the frame and release associated memory. * * @see Renderer, SwapChain::setFrameScheduledCallback @@ -69,6 +70,7 @@ class UTILS_PUBLIC PresentCallable { public: using PresentFn = void(*)(bool presentFrame, void* user); + static void noopPresent(bool, void*) {} PresentCallable(PresentFn fn, void* user) noexcept; ~PresentCallable() noexcept = default; diff --git a/package/ios/libs/filament/include/backend/Program.h b/package/ios/libs/filament/include/backend/Program.h index 7cec72cd..431e939e 100644 --- a/package/ios/libs/filament/include/backend/Program.h +++ b/package/ios/libs/filament/include/backend/Program.h @@ -20,17 +20,21 @@ #include #include #include -#include #include -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers +#include +#include +#include +#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { class Program { @@ -40,36 +44,40 @@ class Program { static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT; static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT; - struct Sampler { - utils::CString name = {}; // name of the sampler in the shader - uint32_t binding = 0; // binding point of the sampler in the shader + struct Descriptor { + utils::CString name; + DescriptorType type; + descriptor_binding_t binding; }; - struct SamplerGroupData { - utils::FixedCapacityVector samplers; - ShaderStageFlags stageFlags = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; - }; + using SpecializationConstant = std::variant; + using DescriptorSetLayoutArray = std::array; - struct Uniform { + struct Uniform { // For ES2 support utils::CString name; // full qualified name of the uniform field uint16_t offset; // offset in 'uint32_t' into the uniform buffer uint8_t size; // >1 for arrays UniformType type; // uniform type }; - using UniformBlockInfo = std::array; - using UniformInfo = utils::FixedCapacityVector; - using SamplerGroupInfo = std::array; + using DescriptorBindingsInfo = utils::FixedCapacityVector; + using DescriptorSetInfo = std::array; + using SpecializationConstantsInfo = utils::FixedCapacityVector; using ShaderBlob = utils::FixedCapacityVector; using ShaderSource = std::array; + using AttributesInfo = utils::FixedCapacityVector>; + using UniformInfo = utils::FixedCapacityVector; + using BindingUniformsInfo = utils::FixedCapacityVector< + std::tuple>; + Program() noexcept; Program(const Program& rhs) = delete; Program& operator=(const Program& rhs) = delete; Program(Program&& rhs) noexcept; - Program& operator=(Program&& rhs) noexcept = delete; + Program& operator=(Program&& rhs) noexcept; ~Program() noexcept; @@ -77,45 +85,22 @@ class Program { // sets the material name and variant for diagnostic purposes only Program& diagnostics(utils::CString const& name, - utils::Invocable&& logger); + utils::Invocable&& logger); - // sets one of the program's shader (e.g. vertex, fragment) + // Sets one of the program's shader (e.g. vertex, fragment) // string-based shaders are null terminated, consequently the size parameter must include the // null terminating character. Program& shader(ShaderStage shader, void const* data, size_t size); - // sets the language of the shader sources provided with shader() (defaults to ESSL3) + // Sets the language of the shader sources provided with shader() (defaults to ESSL3) Program& shaderLanguage(ShaderLanguage shaderLanguage); - // Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is - // not permitted in glsl. The backend needs a way to associate a uniform block - // to a binding point. - Program& uniformBlockBindings( - utils::FixedCapacityVector> const& uniformBlockBindings) noexcept; - - // Note: This is only needed for GLES2.0, this is used to emulate UBO. This function tells - // the program everything it needs to know about the uniforms at a given binding - Program& uniforms(uint32_t index, UniformInfo const& uniforms) noexcept; - - // Note: This is only needed for GLES2.0. - Program& attributes( - utils::FixedCapacityVector> attributes) noexcept; - - // sets the 'bindingPoint' sampler group descriptor for this program. - // 'samplers' can be destroyed after this call. - // This effectively associates a set of (BindingPoints, index) to a texture unit in the shader. - // Or more precisely, what layout(binding=) is set to in GLSL. - Program& setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags, - Sampler const* samplers, size_t count) noexcept; - - struct SpecializationConstant { - using Type = std::variant; - uint32_t id; // id set in glsl - Type value; // value and type - }; + // Descriptor binding (set, binding, type -> shader name) info + Program& descriptorBindings(backend::descriptor_set_t set, + DescriptorBindingsInfo descriptorBindings) noexcept; - Program& specializationConstants( - utils::FixedCapacityVector specConstants) noexcept; + Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept; struct PushConstant { utils::CString name; @@ -129,33 +114,50 @@ class Program { Program& multiview(bool multiview) noexcept; - ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } - ShaderSource& getShadersSource() noexcept { return mShadersSource; } - - UniformBlockInfo const& getUniformBlockBindings() const noexcept { return mUniformBlocks; } - UniformBlockInfo& getUniformBlockBindings() noexcept { return mUniformBlocks; } + // For ES2 support only... + Program& uniforms(uint32_t index, utils::CString name, UniformInfo uniforms); + Program& attributes(AttributesInfo attributes) noexcept; - SamplerGroupInfo const& getSamplerGroupInfo() const { return mSamplerGroups; } - SamplerGroupInfo& getSamplerGroupInfo() { return mSamplerGroups; } + // + // Getters for program construction... + // - auto const& getBindingUniformInfo() const { return mBindingUniformInfo; } - auto& getBindingUniformInfo() { return mBindingUniformInfo; } - - auto const& getAttributes() const { return mAttributes; } - auto& getAttributes() { return mAttributes; } + ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } + ShaderSource& getShadersSource() noexcept { return mShadersSource; } utils::CString const& getName() const noexcept { return mName; } utils::CString& getName() noexcept { return mName; } auto const& getShaderLanguage() const { return mShaderLanguage; } - utils::FixedCapacityVector const& getSpecializationConstants() const noexcept { + uint64_t getCacheId() const noexcept { return mCacheId; } + + bool isMultiview() const noexcept { return mMultiview; } + + CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } + + SpecializationConstantsInfo const& getSpecializationConstants() const noexcept { return mSpecializationConstants; } - utils::FixedCapacityVector& getSpecializationConstants() noexcept { + + SpecializationConstantsInfo& getSpecializationConstants() noexcept { return mSpecializationConstants; } + DescriptorSetInfo& getDescriptorBindings() noexcept { + return mDescriptorBindings; + } + + inline Program& descriptorLayout(backend::descriptor_set_t set, + DescriptorSetLayout descriptorLayout) noexcept { + mDescriptorLayouts[set] = std::move(descriptorLayout); + return *this; + } + + const DescriptorSetLayoutArray& getDescriptorSetLayouts() const noexcept { + return mDescriptorLayouts; + } + utils::FixedCapacityVector const& getPushConstants( ShaderStage stage) const noexcept { return mPushConstants[static_cast(stage)]; @@ -165,27 +167,34 @@ class Program { return mPushConstants[static_cast(stage)]; } - uint64_t getCacheId() const noexcept { return mCacheId; } + auto const& getBindingUniformInfo() const { return mBindingUniformsInfo; } + auto& getBindingUniformInfo() { return mBindingUniformsInfo; } - bool isMultiview() const noexcept { return mMultiview; } - - CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } + auto const& getAttributes() const { return mAttributes; } + auto& getAttributes() { return mAttributes; } private: friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder); - UniformBlockInfo mUniformBlocks = {}; - SamplerGroupInfo mSamplerGroups = {}; ShaderSource mShadersSource; ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3; utils::CString mName; uint64_t mCacheId{}; - utils::Invocable mLogger; - utils::FixedCapacityVector mSpecializationConstants; - std::array, SHADER_TYPE_COUNT> mPushConstants; - utils::FixedCapacityVector> mAttributes; - std::array mBindingUniformInfo; CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; + utils::Invocable + mLogger; + SpecializationConstantsInfo mSpecializationConstants; + std::array, SHADER_TYPE_COUNT> mPushConstants; + DescriptorSetInfo mDescriptorBindings; + + // Descriptions for descriptor set layouts that may be used for this Program, which + // can be useful for attempting to compile the pipeline ahead of time. + DescriptorSetLayoutArray mDescriptorLayouts; + + // For ES2 support only + AttributesInfo mAttributes; + BindingUniformsInfo mBindingUniformsInfo; + // Indicates the current engine was initialized with multiview stereo, and the variant for this // program contains STE flag. This will be referred later for the OpenGL shader compiler to // determine whether shader code replacement for the num_views should be performed. diff --git a/package/ios/libs/filament/include/backend/TargetBufferInfo.h b/package/ios/libs/filament/include/backend/TargetBufferInfo.h index c4d284cc..c2538161 100644 --- a/package/ios/libs/filament/include/backend/TargetBufferInfo.h +++ b/package/ios/libs/filament/include/backend/TargetBufferInfo.h @@ -19,26 +19,26 @@ #include -#include +#include #include #include +namespace utils::io { +class ostream; +} // namespace utils::io + namespace filament::backend { //! \privatesection struct TargetBufferInfo { // note: the parameters of this constructor are not in the order of this structure's fields - TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept - : handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) { - } - - TargetBufferInfo(Handle handle, uint8_t level, uint16_t layer) noexcept - : handle(handle), level(level), layer(layer) { + TargetBufferInfo(Handle handle, uint8_t const level, uint16_t const layer) noexcept + : handle(std::move(handle)), level(level), layer(layer) { } - TargetBufferInfo(Handle handle, uint8_t level) noexcept + TargetBufferInfo(Handle handle, uint8_t const level) noexcept : handle(handle), level(level) { } @@ -51,14 +51,15 @@ struct TargetBufferInfo { // texture to be used as render target Handle handle; - // Starting layer index for multiview. This value is only used when the `layerCount` for the - // render target is greater than 1. - uint8_t baseViewIndex = 0; - // level to be used uint8_t level = 0; - // For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping + // - For cubemap textures, this indicates the face of the cubemap. See TextureCubemapFace for + // the face->layer mapping) + // - For 2d array, cubemap array, and 3d textures, this indicates an index of a single layer of + // them. + // - For multiview textures (i.e., layerCount for the RenderTarget is greater than 1), this + // indicates a starting layer index of the current 2d array texture for multiview. uint16_t layer = 0; }; @@ -73,11 +74,11 @@ class MRT { TargetBufferInfo mInfos[MAX_SUPPORTED_RENDER_TARGET_COUNT]; public: - TargetBufferInfo const& operator[](size_t i) const noexcept { + TargetBufferInfo const& operator[](size_t const i) const noexcept { return mInfos[i]; } - TargetBufferInfo& operator[](size_t i) noexcept { + TargetBufferInfo& operator[](size_t const i) noexcept { return mInfos[i]; } @@ -103,7 +104,7 @@ class MRT { // this is here for backward compatibility MRT(Handle handle, uint8_t level, uint16_t layer) noexcept - : mInfos{{ handle, level, layer, 0 }} { + : mInfos{{ handle, level, layer }} { } }; diff --git a/package/ios/libs/filament/include/backend/platforms/AndroidNdk.h b/package/ios/libs/filament/include/backend/platforms/AndroidNdk.h new file mode 100644 index 00000000..a3766710 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/AndroidNdk.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILAMENT_BACKEND_ANDROIDNDK_H +#define FILAMENT_BACKEND_ANDROIDNDK_H + +#include +#include + +#define FILAMENT_REQUIRES_API(x) __attribute__((__availability__(android,introduced=x))) + +#define FILAMENT_USE_DLSYM(api) (__ANDROID_API__ < (api)) + +namespace filament::backend { + +class AndroidNdk { +public: + AndroidNdk(); + +#if FILAMENT_USE_DLSYM(26) + + static void AHardwareBuffer_acquire( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ndk.AHardwareBuffer_acquire(buffer); + } + + static void AHardwareBuffer_release( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ndk.AHardwareBuffer_release(buffer); + } + + static void AHardwareBuffer_describe( + AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) { + ndk.AHardwareBuffer_describe(buffer, desc); + } + +#else + + static void AHardwareBuffer_acquire( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ::AHardwareBuffer_acquire(buffer); + } + static void AHardwareBuffer_release( + AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) { + ::AHardwareBuffer_release(buffer); + } + static void AHardwareBuffer_describe( + AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) { + ::AHardwareBuffer_describe(buffer, desc); + } + +#endif + +private: +#if FILAMENT_USE_DLSYM(26) + static struct Ndk { + void (*AHardwareBuffer_acquire)(AHardwareBuffer*); + void (*AHardwareBuffer_release)(AHardwareBuffer*); + void (*AHardwareBuffer_describe)(AHardwareBuffer const*, AHardwareBuffer_Desc*); + } ndk; +#endif +}; + +} // filament::backend + +#endif //FILAMENT_BACKEND_ANDROIDNDK_H diff --git a/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h b/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h index e00930c9..d71548e3 100644 --- a/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h +++ b/package/ios/libs/filament/include/backend/platforms/OpenGLPlatform.h @@ -23,9 +23,11 @@ #include #include +#include #include #include +#include namespace filament::backend { @@ -51,12 +53,23 @@ class OpenGLPlatform : public Platform { ~OpenGLPlatform() noexcept override; public: - struct ExternalTexture { - unsigned int target; // GLenum target - unsigned int id; // GLuint id + unsigned int target; // GLenum target + unsigned int id; // GLuint id }; + /** + * Return the OpenGL vendor string of the specified Driver instance. + * @return The GL_VENDOR string + */ + static utils::CString getVendorString(Driver const* UTILS_NONNULL driver); + + /** + * Return the OpenGL vendor string of the specified Driver instance + * @return The GL_RENDERER string + */ + static utils::CString getRendererString(Driver const* UTILS_NONNULL driver); + /** * Called by the driver to destroy the OpenGL context. This should clean up any windows * or buffers from initialization. This is for instance where `eglDestroyContext` would be @@ -72,6 +85,15 @@ class OpenGLPlatform : public Platform { */ virtual bool isSRGBSwapChainSupported() const noexcept; + /** + * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_MSAA_*_SAMPLES flag. + * The default implementation returns false. + * + * @param samples The number of samples + * @return true if SWAP_CHAIN_CONFIG_MSAA_*_SAMPLES is supported, false otherwise. + */ + virtual bool isMSAASwapChainSupported(uint32_t samples) const noexcept; + /** * Return whether protected contexts are supported by this backend. * If protected context are supported, the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag can be @@ -90,7 +112,7 @@ class OpenGLPlatform : public Platform { * */ virtual SwapChain* UTILS_NULLABLE createSwapChain( - void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0; + void* UTILS_NULLABLE nativeWindow, uint64_t flags) = 0; /** * Called by the driver create a headless SwapChain. @@ -104,7 +126,7 @@ class OpenGLPlatform : public Platform { * A void* might be enough. */ virtual SwapChain* UTILS_NULLABLE createSwapChain( - uint32_t width, uint32_t height, uint64_t flags) noexcept = 0; + uint32_t width, uint32_t height, uint64_t flags) = 0; /** * Called by the driver to destroys the SwapChain @@ -186,7 +208,7 @@ class OpenGLPlatform : public Platform { */ virtual bool makeCurrent(ContextType type, SwapChain* UTILS_NONNULL drawSwapChain, - SwapChain* UTILS_NONNULL readSwapChain) noexcept = 0; + SwapChain* UTILS_NONNULL readSwapChain) = 0; /** * Called by the driver to make the OpenGL context active on the calling thread and bind @@ -206,7 +228,7 @@ class OpenGLPlatform : public Platform { SwapChain* UTILS_NONNULL drawSwapChain, SwapChain* UTILS_NONNULL readSwapChain, utils::Invocable preContextChange, - utils::Invocable postContextChange) noexcept; + utils::Invocable postContextChange); /** * Called by the backend just before calling commit() @@ -263,6 +285,26 @@ class OpenGLPlatform : public Platform { */ virtual backend::FenceStatus waitFence(Fence* UTILS_NONNULL fence, uint64_t timeout) noexcept; + // -------------------------------------------------------------------------------------------- + // Sync support + + /** + * Creates a Sync. These can be used for frame synchronization externally + * (certain platform implementations can be exported to handles that can + * be used in other processes). + * + * @return A Sync object. + */ + virtual Platform::Sync* UTILS_NONNULL createSync() noexcept; + + /** + * Destroys a sync. If called with a sync not created by this platform + * object, this will lead to undefined behavior. + * + * @param sync The sync to destroy, that was created by this platform + * instance. + */ + virtual void destroySync(Platform::Sync* UTILS_NONNULL sync) noexcept; // -------------------------------------------------------------------------------------------- // Streaming support @@ -307,6 +349,13 @@ class OpenGLPlatform : public Platform { virtual void updateTexImage(Stream* UTILS_NONNULL stream, int64_t* UTILS_NONNULL timestamp) noexcept; + /** + * Returns the transform matrix of the texture attached to the stream. + * @param stream Stream to get the transform matrix from + * @param uvTransform Output parameter: Transform matrix of the image bound to the texture. Returns identity if not supported. + */ + virtual math::mat3f getTransformMatrix(Stream* UTILS_NONNULL stream) noexcept; + // -------------------------------------------------------------------------------------------- // External Image support @@ -324,36 +373,45 @@ class OpenGLPlatform : public Platform { * Destroys an external texture handle and associated data. * @param texture a pointer to the handle to destroy. */ - virtual void destroyExternalImage(ExternalTexture* UTILS_NONNULL texture) noexcept; + virtual void destroyExternalImageTexture(ExternalTexture* UTILS_NONNULL texture) noexcept; // called on the application thread to allow Filament to take ownership of the image /** * Takes ownership of the externalImage. The externalImage parameter depends on the Platform's - * concrete implementation. Ownership is released when destroyExternalImage() is called. + * concrete implementation. Ownership is released when destroyExternalImageTexture() is called. * * WARNING: This is called synchronously from the application thread (NOT the Driver thread) * * @param externalImage A token representing the platform's external image. * @see destroyExternalImage + * @{ */ virtual void retainExternalImage(void* UTILS_NONNULL externalImage) noexcept; + virtual void retainExternalImage(ExternalImageHandleRef externalImage) noexcept; + /** @}*/ + /** * Called to bind the platform-specific externalImage to an ExternalTexture. * ExternalTexture::id is guaranteed to be bound when this method is called and ExternalTexture * is updated with new values for id/target if necessary. * * WARNING: this method is not allowed to change the bound texture, or must restore the previous - * binding upon return. This is to avoid problem with a backend doing state caching. + * binding upon return. This is to avoid a problem with a backend doing state caching. * * @param externalImage The platform-specific external image. * @param texture an in/out pointer to ExternalTexture, id and target can be updated if necessary. * @return true on success, false on error. + * @{ */ virtual bool setExternalImage(void* UTILS_NONNULL externalImage, ExternalTexture* UTILS_NONNULL texture) noexcept; + virtual bool setExternalImage(ExternalImageHandleRef externalImage, + ExternalTexture* UTILS_NONNULL texture) noexcept; + /** @}*/ + /** * The method allows platforms to convert a user-supplied external image object into a new type * (e.g. HardwareBuffer => EGLImage). The default implementation returns source. @@ -372,7 +430,7 @@ class OpenGLPlatform : public Platform { virtual bool isExtraContextSupported() const noexcept; /** - * Creates an OpenGL context with the same configuration than the main context and makes it + * Creates an OpenGL context with the same configuration as the main context and makes it * current to the current thread. Must not be called from the main driver thread. * createContext() is only supported if isExtraContextSupported() returns true. * These additional contexts will be automatically terminated in terminate. @@ -385,7 +443,7 @@ class OpenGLPlatform : public Platform { /** * Detach and destroy the current context if any and releases all resources associated to - * this thread. + * this thread. This must be called from the same thread where createContext() was called. */ virtual void releaseContext() noexcept; }; diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformCocoaGL.h b/package/ios/libs/filament/include/backend/platforms/PlatformCocoaGL.h index 97c9c3ce..c424ec4f 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformCocoaGL.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformCocoaGL.h @@ -17,7 +17,6 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_COCOA_GL_H -#include #include #include @@ -34,12 +33,14 @@ class PlatformCocoaGL : public OpenGLPlatform { PlatformCocoaGL(); ~PlatformCocoaGL() noexcept override; + ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept; + protected: // -------------------------------------------------------------------------------------------- // Platform Interface Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; // Currently returns 0 int getOSVersion() const noexcept override; @@ -57,12 +58,14 @@ class PlatformCocoaGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; - OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; - void destroyExternalImage(ExternalTexture* texture) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; void retainExternalImage(void* externalImage) noexcept override; bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept override; + void retainExternalImage(ExternalImageHandleRef externalImage) noexcept override; + bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override; private: PlatformCocoaGLImpl* pImpl = nullptr; diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h b/package/ios/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h index e7f1d1ff..94ca86d1 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformCocoaTouchGL.h @@ -32,11 +32,13 @@ class PlatformCocoaTouchGL : public OpenGLPlatform { PlatformCocoaTouchGL(); ~PlatformCocoaTouchGL() noexcept override; + ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept; + // -------------------------------------------------------------------------------------------- // Platform Interface Driver* createDriver(void* sharedGLContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; int getOSVersion() const noexcept final { return 0; } @@ -53,13 +55,15 @@ class PlatformCocoaTouchGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; - OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; - void destroyExternalImage(ExternalTexture* texture) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; void retainExternalImage(void* externalImage) noexcept override; bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept override; + void retainExternalImage(ExternalImageHandleRef externalImage) noexcept override; + bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override; private: PlatformCocoaTouchGLImpl* pImpl = nullptr; diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformEGL.h b/package/ios/libs/filament/include/backend/platforms/PlatformEGL.h index ef687653..8b8f2a95 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformEGL.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformEGL.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -47,6 +48,11 @@ class PlatformEGL : public OpenGLPlatform { // Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default. virtual bool isOpenGL() const noexcept; + /** + * Creates an ExternalImage from a EGLImageKHR + */ + ExternalImageHandle UTILS_PUBLIC createExternalImage(EGLImageKHR eglImage) noexcept; + protected: // -------------------------------------------------------------------------------------------- // Helper for EGL configs and attributes parameters @@ -75,8 +81,7 @@ class PlatformEGL : public OpenGLPlatform { * Initializes EGL, creates the OpenGL context and returns a concrete Driver implementation * that supports OpenGL/OpenGL ES. */ - Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + Driver* createDriver(void* sharedContext, const DriverConfig& driverConfig) override; /** * This returns zero. This method can be overridden to return something more useful. @@ -94,22 +99,19 @@ class PlatformEGL : public OpenGLPlatform { void terminate() noexcept override; bool isProtectedContextSupported() const noexcept override; - bool isSRGBSwapChainSupported() const noexcept override; - SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; - SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; - void destroySwapChain(SwapChain* swapChain) noexcept override; + bool isMSAASwapChainSupported(uint32_t samples) const noexcept override; bool isSwapChainProtected(SwapChain* swapChain) noexcept override; ContextType getCurrentContextType() const noexcept override; bool makeCurrent(ContextType type, SwapChain* drawSwapChain, - SwapChain* readSwapChain) noexcept override; + SwapChain* readSwapChain) override; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain, utils::Invocable preContextChange, - utils::Invocable postContextChange) noexcept override; + utils::Invocable postContextChange) override; void commit(SwapChain* swapChain) noexcept override; @@ -118,12 +120,13 @@ class PlatformEGL : public OpenGLPlatform { void destroyFence(Fence* fence) noexcept override; FenceStatus waitFence(Fence* fence, uint64_t timeout) noexcept override; - OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; - void destroyExternalImage(ExternalTexture* texture) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; bool setExternalImage(void* externalImage, ExternalTexture* texture) noexcept override; + bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override; /** - * Logs glGetError() to slog.e + * Logs glGetError() to LOG(ERROR) * @param name a string giving some context on the error. Typically __func__. */ static void logEglError(const char* name) noexcept; @@ -143,25 +146,18 @@ class PlatformEGL : public OpenGLPlatform { EGLContext getContextForType(ContextType type) const noexcept; // makes the draw and read surface current without changing the current context - EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { + EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { return egl.makeCurrent(drawSurface, readSurface); } // makes context current and set draw and read surfaces to EGL_NO_SURFACE - EGLBoolean makeCurrent(EGLContext context) noexcept { + EGLBoolean makeCurrent(EGLContext context) { return egl.makeCurrent(context, mEGLDummySurface, mEGLDummySurface); } - // TODO: this should probably use getters instead. - EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; - EGLContext mEGLContext = EGL_NO_CONTEXT; - EGLContext mEGLContextProtected = EGL_NO_CONTEXT; - EGLSurface mEGLDummySurface = EGL_NO_SURFACE; - ContextType mCurrentContextType = ContextType::NONE; - // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false - EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; - Config mContextAttribs; - std::vector mAdditionalContexts; + EGLDisplay getEglDisplay() const noexcept { return mEGLDisplay; } + EGLConfig getEglConfig() const noexcept { return mEGLConfig; } + EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const; // supported extensions detected at runtime struct { @@ -178,7 +174,11 @@ class PlatformEGL : public OpenGLPlatform { } egl; } ext; - struct SwapChainEGL : public Platform::SwapChain { + struct SwapChainEGL : public SwapChain { + SwapChainEGL(PlatformEGL const& platform, void* nativeWindow, uint64_t flags); + SwapChainEGL(PlatformEGL const& platform, uint32_t width, uint32_t height, uint64_t flags); + void terminate(PlatformEGL& platform); + EGLSurface sur = EGL_NO_SURFACE; Config attribs{}; EGLNativeWindowType nativeWindow{}; @@ -188,10 +188,31 @@ class PlatformEGL : public OpenGLPlatform { void initializeGlExtensions() noexcept; -protected: - EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; + struct ExternalImageEGL : public ExternalImage { + EGLImageKHR eglImage = EGL_NO_IMAGE; + protected: + ~ExternalImageEGL() override; + }; private: + // prevent derived classes' implementations to call through + [[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override; + [[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + + EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; + + EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; + EGLContext mEGLContext = EGL_NO_CONTEXT; + EGLContext mEGLContextProtected = EGL_NO_CONTEXT; + EGLSurface mEGLDummySurface = EGL_NO_SURFACE; + ContextType mCurrentContextType = ContextType::NONE; + // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false + EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; + Config mContextAttribs; + std::vector mAdditionalContexts; + bool mMSAA4XSupport = false; + class EGL { EGLDisplay& mEGLDisplay; EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE; @@ -200,12 +221,14 @@ class PlatformEGL : public OpenGLPlatform { public: explicit EGL(EGLDisplay& dpy) : mEGLDisplay(dpy) {} EGLBoolean makeCurrent(EGLContext context, - EGLSurface drawSurface, EGLSurface readSurface) noexcept; + EGLSurface drawSurface, EGLSurface readSurface); - EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { + EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { return makeCurrent(mCurrentContext, drawSurface, readSurface); } } egl{ mEGLDisplay }; + + bool checkIfMSAASwapChainSupported(uint32_t samples) const noexcept; }; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h b/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h index c3cc7da8..9410a681 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformEGLAndroid.h @@ -17,12 +17,18 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H #define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H +#include "AndroidNdk.h" + #include +#include #include #include #include #include +#include + +#include #include @@ -37,13 +43,45 @@ class ExternalStreamManagerAndroid; * A concrete implementation of OpenGLPlatform and subclass of PlatformEGL that supports * EGL on Android. It adds Android streaming functionality to PlatformEGL. */ -class PlatformEGLAndroid : public PlatformEGL { +class PlatformEGLAndroid : public PlatformEGL, public AndroidNdk { public: PlatformEGLAndroid() noexcept; ~PlatformEGLAndroid() noexcept override; + /** + * Creates an ExternalImage from a EGLImageKHR + */ + ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept; + + struct UTILS_PUBLIC ExternalImageDescAndroid { + uint32_t width; // Texture width + uint32_t height; // Texture height + TextureFormat format;// Texture format + TextureUsage usage; // Texture usage flags + }; + + ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(ExternalImageHandle externalImage) noexcept; + + /** + * Converts a sync to an external file descriptor, if possible. Accepts an + * opaque handle to a sync, as well as a pointer to where the fd should be + * stored. + * @param sync The sync to be converted to a file descriptor. + * @param fd A pointer to where the file descriptor should be stored. + * @return `true` on success, `false` on failure. The default implementation + * returns `false`. + */ + bool convertSyncToFd(Sync* sync, int* fd) noexcept; + protected: + struct { + struct { + bool ANDROID_presentation_time = false; + bool ANDROID_get_frame_timestamps = false; + bool ANDROID_native_fence_sync = false; + } egl; + } ext; // -------------------------------------------------------------------------------------------- // Platform Interface @@ -55,11 +93,25 @@ class PlatformEGLAndroid : public PlatformEGL { int getOSVersion() const noexcept override; Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; + + bool isCompositorTimingSupported() const noexcept override; + + bool queryCompositorTiming(SwapChain const* swapchain, + CompositorTiming* outCompositorTiming) const noexcept override; + + bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override; + + bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId, + FrameTimestamps* outFrameTimestamps) const noexcept override; // -------------------------------------------------------------------------------------------- // OpenGLPlatform Interface + struct SyncEGLAndroid : public Sync { + EGLSyncKHR sync; + }; + void terminate() noexcept override; void beginFrame( @@ -75,12 +127,14 @@ class PlatformEGLAndroid : public PlatformEGL { */ void setPresentationTime(int64_t presentationTimeInNanosecond) noexcept override; - Stream* createStream(void* nativeStream) noexcept override; void destroyStream(Stream* stream) noexcept override; + Sync* createSync() noexcept override; + void destroySync(Sync* sync) noexcept override; void attach(Stream* stream, intptr_t tname) noexcept override; void detach(Stream* stream) noexcept override; void updateTexImage(Stream* stream, int64_t* timestamp) noexcept override; + math::mat3f getTransformMatrix(Stream* stream) noexcept override; /** * Converts a AHardwareBuffer to EGLImage @@ -89,19 +143,61 @@ class PlatformEGLAndroid : public PlatformEGL { */ AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override; + ExternalTexture* createExternalImageTexture() noexcept override; + void destroyExternalImageTexture(ExternalTexture* texture) noexcept override; + + struct ExternalImageEGLAndroid : public ExternalImageEGL { + AHardwareBuffer* aHardwareBuffer = nullptr; + uint32_t width; // Texture width + uint32_t height; // Texture height + TextureFormat format;// Texture format + TextureUsage usage; // Texture usage flags + bool sRGB = false; + + protected: + ~ExternalImageEGLAndroid() override; + }; + + bool setExternalImage(ExternalImageHandleRef externalImage, + ExternalTexture* texture) noexcept override; + bool setImage(ExternalImageEGLAndroid const* eglExternalImage, + ExternalTexture* texture) noexcept; + + bool makeCurrent(ContextType type, + SwapChain* drawSwapChain, + SwapChain* readSwapChain) override; + private: + struct SwapChainEGLAndroid; + struct AndroidDetails; + + // prevent derived classes' implementations to call through + [[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override; + [[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + + bool isProducerThrottlingControlSupported() const; + + int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const; + struct InitializeJvmForPerformanceManagerIfNeeded { InitializeJvmForPerformanceManagerIfNeeded(); }; + struct ExternalTextureAndroid : public ExternalTexture { + EGLImageKHR eglImage = EGL_NO_IMAGE; + }; + int mOSVersion; ExternalStreamManagerAndroid& mExternalStreamManager; + AndroidDetails& mAndroidDetails; InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded; utils::PerformanceHintManager mPerformanceHintManager; utils::PerformanceHintManager::Session mPerformanceHintSession; - using clock = std::chrono::high_resolution_clock; clock::time_point mStartTimeOfActualWork; + SwapChainEGLAndroid* mCurrentDrawSwapChain{}; + bool mAssertNativeWindowIsValid = false; }; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformEGLHeadless.h b/package/ios/libs/filament/include/backend/platforms/PlatformEGLHeadless.h index 40d285b9..300748e0 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformEGLHeadless.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformEGLHeadless.h @@ -29,7 +29,7 @@ class PlatformEGLHeadless : public PlatformEGL { PlatformEGLHeadless() noexcept; Driver* createDriver(void* sharedContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const Platform::DriverConfig& driverConfig) override; protected: bool isOpenGL() const noexcept override; diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformGLX.h b/package/ios/libs/filament/include/backend/platforms/PlatformGLX.h index 796e27a1..8d0eda33 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformGLX.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformGLX.h @@ -26,7 +26,10 @@ #include +#include +#include #include +#include namespace filament::backend { @@ -39,7 +42,7 @@ class PlatformGLX : public OpenGLPlatform { // Platform Interface Driver* createDriver(void* sharedGLContext, - const DriverConfig& driverConfig) noexcept override; + const DriverConfig& driverConfig) override; int getOSVersion() const noexcept final override { return 0; } @@ -48,18 +51,26 @@ class PlatformGLX : public OpenGLPlatform { void terminate() noexcept override; + bool isExtraContextSupported() const noexcept override; + void createContext(bool shared) override; + void releaseContext() noexcept override; + SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; private: - Display *mGLXDisplay; - GLXContext mGLXContext; - GLXFBConfig* mGLXConfig; + Display* mGLXDisplay; + GLXContext mGLXContext{}; + GLXFBConfig mGLXConfig{}; GLXPbuffer mDummySurface; std::vector mPBuffers; + + // Variables for shared contexts + std::unordered_map mAdditionalContexts; + std::shared_mutex mAdditionalContextsLock; }; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h b/package/ios/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h new file mode 100644 index 00000000..2635ea67 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/PlatformMetal-ObjC.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_OBJC_H +#define TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_OBJC_H + +#import + +namespace filament::backend { + +struct MetalDevice { + id device; +}; + +struct MetalCommandQueue { + id commandQueue; +}; + +struct MetalCommandBuffer { + id commandBuffer; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_OBJC_H diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformMetal.h b/package/ios/libs/filament/include/backend/platforms/PlatformMetal.h new file mode 100644 index 00000000..a2e3ac25 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/PlatformMetal.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H +#define TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H + +#include +#include + +namespace filament::backend { + +struct PlatformMetalImpl; + +// In order for this header to be compatible with Objective-C and C++, we use these wrappers around +// id objects. +// See PlatformMetal-Objc.h. +struct MetalDevice; +struct MetalCommandQueue; +struct MetalCommandBuffer; + +class PlatformMetal final : public Platform { +public: + PlatformMetal(); + ~PlatformMetal() noexcept override; + + Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override; + int getOSVersion() const noexcept override { return 0; } + + /** + * Optionally initializes the Metal platform by acquiring resources necessary for rendering. + * + * This method attempts to acquire a Metal device and command queue, returning true if both are + * successfully obtained, or false otherwise. Typically, these objects are acquired when + * the Metal backend is initialized. This method allows clients to check for their availability + * earlier. + * + * Calling initialize() is optional and safe to do so multiple times. After initialize() returns + * true, subsequent calls will continue to return true but have no effect. + * + * initialize() must be called from the main thread. + * + * @returns true if the device and command queue have been successfully obtained; false + * otherwise. + */ + bool initialize() noexcept; + + /** + * Obtain the preferred Metal device object for the backend to use. + * + * On desktop platforms, there may be multiple GPUs suitable for rendering, and this method is + * free to decide which one to use. On mobile systems with a single GPU, implementations should + * simply return the result of MTLCreateSystemDefaultDevice(); + * + * createDevice is called by the Metal backend from the backend thread. + */ + virtual void createDevice(MetalDevice& outDevice) noexcept; + + /** + * Create a command submission queue on the Metal device object. + * + * createCommandQueue is called by the Metal backend from the backend thread. + * + * @param device The device which was returned from createDevice() + */ + virtual void createCommandQueue( + MetalDevice& device, MetalCommandQueue& outCommandQueue) noexcept; + + /** + * Obtain a MTLCommandBuffer enqueued on this Platform's MTLCommandQueue. The command buffer is + * guaranteed to execute before all subsequent command buffers created either by Filament, or + * further calls to this method. + * + * createAndEnqueueCommandBuffer must be called from the main thread. + */ + void createAndEnqueueCommandBuffer(MetalCommandBuffer& outCommandBuffer) noexcept; + + /** + * The action to take if a Drawable cannot be acquired. + * + * Each frame rendered requires a CAMetalDrawable texture, which is presented on-screen at the + * completion of each frame. These are limited and provided round-robin style by the system. + * + * setDrawableFailureBehavior must be called from the main thread. + */ + enum class DrawableFailureBehavior : uint8_t { + /** + * Terminates the application and reports an error message (default). + */ + PANIC, + /* + * Aborts execution of the current frame. The Metal backend will attempt to acquire a new + * drawable at the next frame. + */ + ABORT_FRAME + }; + void setDrawableFailureBehavior(DrawableFailureBehavior behavior) noexcept; + DrawableFailureBehavior getDrawableFailureBehavior() const noexcept; + +private: + PlatformMetalImpl* pImpl = nullptr; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PRIVATE_PLATFORMMETAL_H diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformOSMesa.h b/package/ios/libs/filament/include/backend/platforms/PlatformOSMesa.h new file mode 100644 index 00000000..10e1fb18 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/PlatformOSMesa.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H +#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H + +#include + +#include "bluegl/BlueGL.h" + +#if defined(__linux__) +#include +#elif defined(__APPLE__) +#undef GLAPI +#include +#endif + +#include +#include + +namespace filament::backend { + +/** + * A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen + * context that can be used in conjunction with Mesa for software rasterization. + * See https://docs.mesa3d.org/osmesa.html for more information. + */ +class PlatformOSMesa : public OpenGLPlatform { +protected: + // -------------------------------------------------------------------------------------------- + // Platform Interface + + Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) override; + + int getOSVersion() const noexcept final override { return 0; } + + // -------------------------------------------------------------------------------------------- + // OpenGLPlatform Interface + + void terminate() noexcept override; + + SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) override; + void commit(SwapChain* swapChain) noexcept override; + +private: + OSMesaContext mContext; + void* mOsMesaApi = nullptr; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformWGL.h b/package/ios/libs/filament/include/backend/platforms/PlatformWGL.h index e0003156..49c79812 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformWGL.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformWGL.h @@ -38,7 +38,7 @@ class PlatformWGL : public OpenGLPlatform { // Platform Interface Driver* createDriver(void* sharedGLContext, - const Platform::DriverConfig& driverConfig) noexcept override; + const Platform::DriverConfig& driverConfig) override; int getOSVersion() const noexcept final override { return 0; } @@ -53,7 +53,7 @@ class PlatformWGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; protected: @@ -61,8 +61,12 @@ class PlatformWGL : public OpenGLPlatform { HWND mHWnd = NULL; HDC mWhdc = NULL; PIXELFORMATDESCRIPTOR mPfd = {}; - std::vector mAdditionalContexts; std::vector mAttribs; + + // For shared contexts + static constexpr int SHARED_CONTEXT_NUM = 2; + std::vector mAdditionalContexts; + std::atomic mNextFreeSharedContextIndex{0}; }; } // namespace filament::backend diff --git a/package/ios/libs/filament/include/backend/platforms/PlatformWebGL.h b/package/ios/libs/filament/include/backend/platforms/PlatformWebGL.h index 0d83fbb9..21083f58 100644 --- a/package/ios/libs/filament/include/backend/platforms/PlatformWebGL.h +++ b/package/ios/libs/filament/include/backend/platforms/PlatformWebGL.h @@ -33,8 +33,7 @@ class PlatformWebGL : public OpenGLPlatform { // -------------------------------------------------------------------------------------------- // Platform Interface - Driver* createDriver(void* sharedGLContext, - const Platform::DriverConfig& driverConfig) noexcept override; + Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) override; int getOSVersion() const noexcept override; @@ -43,10 +42,10 @@ class PlatformWebGL : public OpenGLPlatform { void terminate() noexcept override; - SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; + SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; - bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override; void commit(SwapChain* swapChain) noexcept override; }; diff --git a/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h b/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h index 2bbe1983..edfdea29 100644 --- a/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h +++ b/package/ios/libs/filament/include/backend/platforms/VulkanPlatform.h @@ -17,6 +17,8 @@ #ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H #define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H +#include +#include #include #include @@ -26,6 +28,9 @@ #include #include +#include +#include +#include #include #include @@ -41,6 +46,10 @@ using SwapChain = Platform::SwapChain; */ struct VulkanPlatformPrivate; +// Forward declare the fence status that will be maintained by the command +// buffer manager. +struct VulkanCmdFence; + /** * A Platform interface that creates a Vulkan backend. */ @@ -71,6 +80,9 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation explicitImageReadyWait = nullptr; }; VulkanPlatform(); @@ -109,7 +119,7 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation fenceStatus) noexcept; + + /** + * Destroys a sync. If called with a sync not created by this platform + * object, this will lead to undefined behavior. + * + * @param sync The sync to destroy, which was created by this platform + * instance. + */ + virtual void destroySync(Platform::Sync* sync) noexcept; + /** * Allows implementers to provide instance extensions that they'd like to include in the * instance creation. @@ -267,13 +303,226 @@ class VulkanPlatform : public Platform, utils::PrivateImplementation fenceStatus; + }; + + /** + * Creates the VkInstance used by Filament's Vulkan backend. + * + * This method can be overridden in subclasses to customize VkInstance creation, such as + * adding application-specific layers or extensions. + * + * The provided `createInfo` contains layers and extensions required by Filament. + * If you override this method and need to modify the `createInfo` struct, you must first + * make a copy of it and modify the copy. + * + * @param createInfo The VkInstanceCreateInfo prepared by Filament. + * @return The created VkInstance, or VK_NULL_HANDLE on failure. + */ + virtual VkInstance createVkInstance(VkInstanceCreateInfo const& createInfo) noexcept; + + /** + * Selects a VkPhysicalDevice (GPU) for Filament's Vulkan backend to use. + * + * This method can be overridden in subclasses to implement custom GPU selection logic. + * For example, an application might override this to prefer a discrete GPU over an + * integrated one based on device properties. + * + * The default implementation selects the first device that meets Filament's requirements. + * + * @param instance The VkInstance to enumerate devices from. + * @return The selected VkPhysicalDevice, or VK_NULL_HANDLE if no suitable device is found. + */ + virtual VkPhysicalDevice selectVkPhysicalDevice(VkInstance instance) noexcept; + + /** + * Creates the VkDevice used by Filament's Vulkan backend. + * + * This method can be overridden in subclasses to customize VkDevice creation, such as + * adding application-specific extensions or enabling features. + * + * The provided `createInfo` contains extensions and features required by Filament. + * If you override this method and need to modify the `createInfo` struct, you must first + * make a copy of it and modify the copy. + * + * @param createInfo The VkDeviceCreateInfo prepared by Filament. + * @return The created VkDevice, or VK_NULL_HANDLE on failure. + */ + virtual VkDevice createVkDevice(VkDeviceCreateInfo const& createInfo) noexcept; - // Platform dependent helper methods using SurfaceBundle = std::tuple; - static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, - uint64_t flags) noexcept; + virtual ExtensionSet getSwapchainInstanceExtensions() const = 0; + virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept = 0; + + virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept; + + /** + * Query if transient attachments are supported by the backend. + */ + bool isTransientAttachmentSupported() const noexcept; + +private: + /** + * Contains information about features that should be requested + * when calling vkCreateDevice, based on feature support from + * vkGetPhysicalDeviceFeatures2. + */ + struct MiscDeviceFeatures { + /** + * This allows creation of a VkGraphicsPipeline without a + * render pass specified. + */ + bool dynamicRendering; + + /** + * Allows creation of a 2d image view, or 2d image view array, + * to be created from a 3d VkImage. + */ + bool imageView2Don3DImage; + }; + + void createInstance(ExtensionSet const& requiredExts) noexcept; + + void queryAndSetDeviceFeatures(Platform::DriverConfig const& driverConfig, + ExtensionSet const& instExts, ExtensionSet const& deviceExts, + void* sharedContext) noexcept; + + void createLogicalDeviceAndQueues(ExtensionSet const& deviceExtensions, + VkPhysicalDeviceFeatures const& features, + VkPhysicalDeviceVulkan11Features const& vk11Features, bool createProtectedQueue, + MiscDeviceFeatures const& requestedFeatures) noexcept; friend struct VulkanPlatformPrivate; }; diff --git a/package/ios/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h new file mode 100644 index 00000000..ba82db81 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformAndroid.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H + +#include "AndroidNdk.h" + +#include +#include + +#include + +#include + +namespace filament::backend { + +class VulkanPlatformAndroid : public VulkanPlatform, public AndroidNdk { +public: + ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, + bool sRGB) noexcept; + + struct UTILS_PUBLIC ExternalImageDescAndroid { + uint32_t width; // Texture width + uint32_t height; // Texture height + TextureFormat format;// Texture format + TextureUsage usage; // Texture usage flags + }; + + VulkanPlatformAndroid(); + + ~VulkanPlatformAndroid() noexcept override; + + ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc( + ExternalImageHandleRef externalImage) const noexcept; + + ExternalImageMetadata extractExternalImageMetadata( + ExternalImageHandleRef image) const override; + + ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override; + + /** + * Converts a sync to an external file descriptor, if possible. Accepts an + * opaque handle to a sync, as well as a pointer to where the fd should be + * stored. + * @param sync The sync to be converted to a file descriptor. + * @param fd A pointer to where the file descriptor should be stored. + * @return `true` on success, `false` on failure. The default implementation + * returns `false`. + */ + bool convertSyncToFd(Sync* sync, int* fd) const noexcept; + + int getOSVersion() const noexcept override; + + void terminate() override; + + Driver* createDriver(void* sharedContext, + DriverConfig const& driverConfig) override; + + + bool isCompositorTimingSupported() const noexcept override; + + bool queryCompositorTiming(SwapChain const* swapchain, + CompositorTiming* outCompositorTiming) const noexcept override; + + bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override; + + bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId, + FrameTimestamps* outFrameTimestamps) const noexcept override; + + +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + + using SurfaceBundle = SurfaceBundle; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; + + VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override; + +private: + struct AndroidDetails; + + struct ExternalImageVulkanAndroid : public ExternalImage { + AHardwareBuffer* aHardwareBuffer = nullptr; + bool sRGB = false; + + protected: + ~ExternalImageVulkanAndroid() override; + }; + + AndroidDetails& mAndroidDetails; + int mOSVersion{}; +}; + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H diff --git a/package/ios/libs/filament/include/backend/platforms/VulkanPlatformApple.h b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformApple.h new file mode 100644 index 00000000..5a7c2119 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformApple.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H + +#include + +namespace filament::backend { + +class VulkanPlatformApple : public VulkanPlatform { +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H diff --git a/package/ios/libs/filament/include/backend/platforms/VulkanPlatformLinux.h b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformLinux.h new file mode 100644 index 00000000..2fb964dd --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformLinux.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H + +#include + +namespace filament::backend { + +class VulkanPlatformLinux : public VulkanPlatform { +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H diff --git a/package/ios/libs/filament/include/backend/platforms/VulkanPlatformWindows.h b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformWindows.h new file mode 100644 index 00000000..0b55dede --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/VulkanPlatformWindows.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H + +#include + +namespace filament::backend { + +class VulkanPlatformWindows : public VulkanPlatform { +protected: + ExtensionSet getSwapchainInstanceExtensions() const override; + SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance, + uint64_t flags) const noexcept override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H diff --git a/package/ios/libs/filament/include/backend/platforms/WebGPUPlatform.h b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatform.h new file mode 100644 index 00000000..ebdb70ac --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatform.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H + +#include + +#if defined(__linux__) && defined(FILAMENT_SUPPORTS_X11) +// Resolve the conflicts between webgpu_cpp.h and X11 defines +#undef Always +#undef Success +#undef None +#undef True +#undef False +#undef Status +#undef Bool +#endif +#include + +#include +#include + +namespace filament::backend { + +/** + * A Platform interface, handling the environment-specific concerns, e.g. OS, for creating a WebGPU + * driver (backend). + */ +class WebGPUPlatform : public Platform { +public: + WebGPUPlatform(); + ~WebGPUPlatform() override = default; + + [[nodiscard]] int getOSVersion() const noexcept final { return 0; } + + [[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; } + + // TODO consider that this functionality is not WebGPU-specific, and thus could be + // placed in a generic place and even reused across backends. Alternatively, + // a 3rd party library could be considered. However, this was a simple and + // quick change and works for now. + // gets the size (height and width) of the surface/window + [[nodiscard]] virtual wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const = 0; + // either returns a valid surface or panics + [[nodiscard]] virtual wgpu::Surface createSurface(void* nativeWindow, uint64_t flags) = 0; + // either returns a valid adapter or panics + [[nodiscard]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface); + // either returns a valid device or panics + [[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter); + + struct Configuration { + wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined; + }; + + [[nodiscard]] virtual Configuration getConfiguration() const noexcept { + return {}; + } + +protected: + [[nodiscard]] Driver* createDriver(void* sharedContext, + const Platform::DriverConfig& driverConfig) override; + + // returns adapter request option variations applicable for the particular + // platform + [[nodiscard]] virtual std::vector getAdapterOptions() = 0; + + // we may consider having the driver own this in the future + wgpu::Instance mInstance; +}; + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H diff --git a/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h new file mode 100644 index 00000000..fc3f33b1 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformAndroid.h @@ -0,0 +1,35 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMANDROID_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMANDROID_H + +#include + +namespace filament::backend { + +class WebGPUPlatformAndroid : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMANDROID_H diff --git a/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformApple.h b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformApple.h new file mode 100644 index 00000000..81f9f089 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformApple.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMAPPLE_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMAPPLE_H + +#include + +namespace filament::backend { + +class WebGPUPlatformApple : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMAPPLE_H diff --git a/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h new file mode 100644 index 00000000..43ad7af6 --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformLinux.h @@ -0,0 +1,35 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMLINUX_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMLINUX_H + +#include + +namespace filament::backend { + +class WebGPUPlatformLinux : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMLINUX_H diff --git a/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h new file mode 100644 index 00000000..cc7f6d9a --- /dev/null +++ b/package/ios/libs/filament/include/backend/platforms/WebGPUPlatformWindows.h @@ -0,0 +1,35 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWINDOWS_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWINDOWS_H + +#include + +namespace filament::backend { + +class WebGPUPlatformWindows : public WebGPUPlatform { +public: + wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const override; + wgpu::Surface createSurface(void* nativeWindow, uint64_t /*flags*/) override; + +protected: + std::vector getAdapterOptions() override; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORMWINDOWS_H diff --git a/package/ios/libs/filament/include/camutils/Manipulator.h b/package/ios/libs/filament/include/camutils/Manipulator.h index 2cc8a4da..5ea49eaf 100644 --- a/package/ios/libs/filament/include/camutils/Manipulator.h +++ b/package/ios/libs/filament/include/camutils/Manipulator.h @@ -109,6 +109,7 @@ class CAMUTILS_PUBLIC Manipulator { vec4 groundPlane; RayCallback raycastCallback; void* raycastUserdata; + bool panning = true; }; struct Builder { @@ -143,6 +144,8 @@ class CAMUTILS_PUBLIC Manipulator { Builder& groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d); //! Plane equation used as a raycast fallback Builder& raycastCallback(RayCallback cb, void* userdata); //! Raycast function for accurate grab-and-pan + Builder& panning(bool enabled); //! Sets whether panning is enabled + /** * Creates a new camera manipulator, either ORBIT, MAP, or FREE_FLIGHT. * diff --git a/package/ios/libs/filament/include/filamat/Enums.h b/package/ios/libs/filament/include/filamat/Enums.h deleted file mode 100644 index 04b5ff8b..00000000 --- a/package/ios/libs/filament/include/filamat/Enums.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_ENUMMANAGER_H -#define TNT_ENUMMANAGER_H - -#include -#include -#include - -#include - -namespace filamat { - -using Property = MaterialBuilder::Property; -using UniformType = MaterialBuilder::UniformType; -using SamplerType = MaterialBuilder::SamplerType; -using SubpassType = MaterialBuilder::SubpassType; -using SamplerFormat = MaterialBuilder::SamplerFormat; -using ParameterPrecision = MaterialBuilder::ParameterPrecision; -using OutputTarget = MaterialBuilder::OutputTarget; -using OutputQualifier = MaterialBuilder::VariableQualifier; -using OutputType = MaterialBuilder::OutputType; -using ConstantType = MaterialBuilder::ConstantType; - -// Convenience methods to convert std::string to Enum and also iterate over Enum values. -class Enums { -public: - - // Returns true if string "s" is a valid string representation of an element of enum T. - template - static bool isValid(const std::string& s) noexcept { - std::unordered_map& map = getMap(); - return map.find(s) != map.end(); - } - - // Return enum matching its string representation. Returns undefined if s is not a valid enum T - // value. You should always call isValid() first to validate a string before calling toEnum(). - template - static T toEnum(const std::string& s) noexcept { - std::unordered_map& map = getMap(); - return map.at(s); - } - - template - static std::string toString(T t) noexcept; - - // Return a map of all values in an enum with their string representation. - template - static std::unordered_map& map() noexcept { - std::unordered_map& map = getMap(); - return map; - }; - -private: - template - static std::unordered_map& getMap() noexcept; - - static std::unordered_map mStringToProperty; - static std::unordered_map mStringToUniformType; - static std::unordered_map mStringToSamplerType; - static std::unordered_map mStringToSubpassType; - static std::unordered_map mStringToSamplerFormat; - static std::unordered_map mStringToSamplerPrecision; - static std::unordered_map mStringToOutputTarget; - static std::unordered_map mStringToOutputQualifier; - static std::unordered_map mStringToOutputType; - static std::unordered_map mStringToConstantType; -}; - -template -std::string Enums::toString(T t) noexcept { - std::unordered_map& map = getMap(); - auto result = std::find_if(map.begin(), map.end(), [t](auto& pair) { - return pair.second == t; - }); - if (result != map.end()) { - return result->first; - } - return ""; -} - -} // namespace filamat - -#endif //TNT_ENUMMANAGER_H diff --git a/package/ios/libs/filament/include/filamat/IncludeCallback.h b/package/ios/libs/filament/include/filamat/IncludeCallback.h deleted file mode 100644 index 659ba289..00000000 --- a/package/ios/libs/filament/include/filamat/IncludeCallback.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_FILAMAT_INCLUDER_H -#define TNT_FILAMAT_INCLUDER_H - -#include - -#include - -namespace filamat { - -struct IncludeResult { - // The include name of the root file, as if it were being included. - // I.e., 'foobar.h' in the case of #include "foobar.h" - const utils::CString includeName; - - // The following fields should be filled out by the IncludeCallback when processing an include, - // or when calling resolveIncludes for the root file. - - // The full contents of the include file. This may contain additional, recursive include - // directives. - utils::CString text; - - // The line number for the first line of text (first line is 0). - size_t lineNumberOffset = 0; - - // The name of the include file. This gets passed as "includerName" for any includes inside of - // source. This field isn't used by the include system; it's up to the callback to give meaning - // to this value and interpret it accordingly. In the case of DirIncluder, this is an empty - // string to represent the root include file, and a canonical path for subsequent included - // files. - utils::CString name; -}; - -/** - * A callback invoked by the include system when an #include "file.h" directive is found. - * - * For example, if a file main.h includes file.h on line 10, then IncludeCallback would be called - * with the following: - * includeCallback("main.h", {.includeName = "file.h" }) - * It's then up to the IncludeCallback to fill out the .text, .name, and (optionally) - * lineNumberOffset fields. - * - * @param includedBy is the value that was given to IncludeResult.name for this source file, or - * the empty string for the root source file. - * @param result is the IncludeResult that the callback should fill out. - * @return true, if the include was resolved successfully, false otherwise. - * - * For an example of implementing this callback, see tools/matc/src/matc/DirIncluder.h. - */ -using IncludeCallback = std::function; - -} // namespace filamat - -#endif diff --git a/package/ios/libs/filament/include/filamat/MaterialBuilder.h b/package/ios/libs/filament/include/filamat/MaterialBuilder.h deleted file mode 100644 index c3045b94..00000000 --- a/package/ios/libs/filament/include/filamat/MaterialBuilder.h +++ /dev/null @@ -1,922 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//! \file - -#ifndef TNT_FILAMAT_MATERIAL_PACKAGE_BUILDER_H -#define TNT_FILAMAT_MATERIAL_PACKAGE_BUILDER_H - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace utils { -class JobSystem; -} - -namespace filament { -class BufferInterfaceBlock; -} - -namespace filamat { - -struct MaterialInfo; -struct Variant; -class ChunkContainer; - -class UTILS_PUBLIC MaterialBuilderBase { -public: - /** - * High-level hint that works in concert with TargetApi to determine the shader models (used to - * generate GLSL) and final output representations (spirv and/or text). - * When generating the GLSL this is used to differentiate OpenGL from OpenGLES, it is also - * used to make some performance adjustments. - */ - enum class Platform { - DESKTOP, - MOBILE, - ALL - }; - - /** - * TargetApi defines which language after transpilation will be used, it is used to - * account for some differences between these languages when generating the GLSL. - */ - enum class TargetApi : uint8_t { - OPENGL = 0x01u, - VULKAN = 0x02u, - METAL = 0x04u, - ALL = OPENGL | VULKAN | METAL - }; - - /* - * Generally we generate GLSL that will be converted to SPIRV, optimized and then - * transpiled to the backend's language such as MSL, ESSL300, GLSL410 or SPIRV, in this - * case the generated GLSL uses ESSL310 or GLSL450 and has Vulkan semantics and - * TargetLanguage::SPIRV must be used. - * - * However, in some cases (e.g. when no optimization is asked) we generate the *final* GLSL - * directly, this GLSL must be ESSL300 or GLSL410 and cannot use any Vulkan syntax, for this - * situation we use TargetLanguage::GLSL. In this case TargetApi is guaranteed to be OPENGL. - * - * Note that TargetLanguage::GLSL is not the common case, as it is generally not used in - * release builds. - * - * Also note that glslang performs semantics analysis on whichever GLSL ends up being generated. - */ - enum class TargetLanguage { - GLSL, // GLSL with OpenGL 4.1 / OpenGL ES 3.0 semantics - SPIRV // GLSL with Vulkan semantics - }; - - enum class Optimization { - NONE, - PREPROCESSOR, - SIZE, - PERFORMANCE - }; - - /** - * Initialize MaterialBuilder. - * - * init must be called first before building any materials. - */ - static void init(); - - /** - * Release internal MaterialBuilder resources. - * - * Call shutdown when finished building materials to release all internal resources. After - * calling shutdown, another call to MaterialBuilder::init must precede another material build. - */ - static void shutdown(); - -protected: - // Looks at platform and target API, then decides on shader models and output formats. - void prepare(bool vulkanSemantics, filament::backend::FeatureLevel featureLevel); - - using ShaderModel = filament::backend::ShaderModel; - Platform mPlatform = Platform::DESKTOP; - TargetApi mTargetApi = (TargetApi) 0; - Optimization mOptimization = Optimization::PERFORMANCE; - bool mPrintShaders = false; - bool mGenerateDebugInfo = false; - bool mIncludeEssl1 = true; - utils::bitset32 mShaderModels; - struct CodeGenParams { - ShaderModel shaderModel; - TargetApi targetApi; - TargetLanguage targetLanguage; - filament::backend::FeatureLevel featureLevel; - }; - std::vector mCodeGenPermutations; - - // Keeps track of how many times MaterialBuilder::init() has been called without a call to - // MaterialBuilder::shutdown(). Internally, glslang does something similar. We keep track for - // ourselves, so we can inform the user if MaterialBuilder::init() hasn't been called before - // attempting to build a material. - static std::atomic materialBuilderClients; -}; - -// Utility function that looks at an Engine backend to determine TargetApi -inline constexpr MaterialBuilderBase::TargetApi targetApiFromBackend( - filament::backend::Backend backend) noexcept { - using filament::backend::Backend; - using TargetApi = MaterialBuilderBase::TargetApi; - switch (backend) { - case Backend::DEFAULT: return TargetApi::ALL; - case Backend::OPENGL: return TargetApi::OPENGL; - case Backend::VULKAN: return TargetApi::VULKAN; - case Backend::METAL: return TargetApi::METAL; - case Backend::NOOP: return TargetApi::OPENGL; - } -} - -/** - * MaterialBuilder builds Filament materials from shader code. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * #include - * using namespace filamat; - * - * // Must be called before any materials can be built. - * MaterialBuilder::init(); - - * MaterialBuilder builder; - * builder - * .name("My material") - * .material("void material (inout MaterialInputs material) {" - * " prepareMaterial(material);" - * " material.baseColor.rgb = float3(1.0, 0.0, 0.0);" - * "}") - * .shading(MaterialBuilder::Shading::LIT) - * .targetApi(MaterialBuilder::TargetApi::ALL) - * .platform(MaterialBuilder::Platform::ALL); - - * Package package = builder.build(); - * if (package.isValid()) { - * // success! - * } - - * // Call when finished building all materials to release internal - * // MaterialBuilder resources. - * MaterialBuilder::shutdown(); - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * @see filament::Material - */ -class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { -public: - MaterialBuilder(); - ~MaterialBuilder(); - - MaterialBuilder(const MaterialBuilder& rhs) = delete; - MaterialBuilder& operator=(const MaterialBuilder& rhs) = delete; - - MaterialBuilder(MaterialBuilder&& rhs) noexcept = default; - MaterialBuilder& operator=(MaterialBuilder&& rhs) noexcept = default; - - static constexpr size_t MATERIAL_VARIABLES_COUNT = 4; - enum class Variable : uint8_t { - CUSTOM0, - CUSTOM1, - CUSTOM2, - CUSTOM3 - // when adding more variables, make sure to update MATERIAL_VARIABLES_COUNT - }; - - using MaterialDomain = filament::MaterialDomain; - using RefractionMode = filament::RefractionMode; - using RefractionType = filament::RefractionType; - using ReflectionMode = filament::ReflectionMode; - using VertexAttribute = filament::VertexAttribute; - - using ShaderQuality = filament::ShaderQuality; - using BlendingMode = filament::BlendingMode; - using BlendFunction = filament::backend::BlendFunction; - using Shading = filament::Shading; - using Interpolation = filament::Interpolation; - using VertexDomain = filament::VertexDomain; - using TransparencyMode = filament::TransparencyMode; - using SpecularAmbientOcclusion = filament::SpecularAmbientOcclusion; - - using AttributeType = filament::backend::UniformType; - using UniformType = filament::backend::UniformType; - using ConstantType = filament::backend::ConstantType; - using SamplerType = filament::backend::SamplerType; - using SubpassType = filament::backend::SubpassType; - using SamplerFormat = filament::backend::SamplerFormat; - using ParameterPrecision = filament::backend::Precision; - using Precision = filament::backend::Precision; - using CullingMode = filament::backend::CullingMode; - using FeatureLevel = filament::backend::FeatureLevel; - using StereoscopicType = filament::backend::StereoscopicType; - using ShaderStage = filament::backend::ShaderStage; - - enum class VariableQualifier : uint8_t { - OUT - }; - - enum class OutputTarget : uint8_t { - COLOR, - DEPTH - }; - - enum class OutputType : uint8_t { - FLOAT, - FLOAT2, - FLOAT3, - FLOAT4 - }; - - struct PreprocessorDefine { - std::string name; - std::string value; - - PreprocessorDefine(std::string name, std::string value) : - name(std::move(name)), value(std::move(value)) {} - }; - using PreprocessorDefineList = std::vector; - - - MaterialBuilder& noSamplerValidation(bool enabled) noexcept; - - //! Enable generation of ESSL 1.0 code in FL0 materials. - MaterialBuilder& includeEssl1(bool enabled) noexcept; - - //! Set the name of this material. - MaterialBuilder& name(const char* name) noexcept; - - //! Set the file name of this material file. Used in error reporting. - MaterialBuilder& fileName(const char* name) noexcept; - - //! Set the shading model. - MaterialBuilder& shading(Shading shading) noexcept; - - //! Set the interpolation mode. - MaterialBuilder& interpolation(Interpolation interpolation) noexcept; - - //! Add a parameter (i.e., a uniform) to this material. - MaterialBuilder& parameter(const char* name, UniformType type, - ParameterPrecision precision = ParameterPrecision::DEFAULT) noexcept; - - //! Add a parameter array to this material. - MaterialBuilder& parameter(const char* name, size_t size, UniformType type, - ParameterPrecision precision = ParameterPrecision::DEFAULT) noexcept; - - //! Add a constant parameter to this material. - template - using is_supported_constant_parameter_t = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value>::type; - template> - MaterialBuilder& constant(const char *name, ConstantType type, T defaultValue = 0); - - /** - * Add a sampler parameter to this material. - * - * When SamplerType::SAMPLER_EXTERNAL is specified, format and precision are ignored. - */ - MaterialBuilder& parameter(const char* name, SamplerType samplerType, - SamplerFormat format = SamplerFormat::FLOAT, - ParameterPrecision precision = ParameterPrecision::DEFAULT, - bool multisample = false) noexcept; - - MaterialBuilder& buffer(filament::BufferInterfaceBlock bib) noexcept; - - //! Custom variables (all float4). - MaterialBuilder& variable(Variable v, const char* name) noexcept; - - /** - * Require a specified attribute. - * - * position is always required and normal depends on the shading model. - */ - MaterialBuilder& require(VertexAttribute attribute) noexcept; - - //! Specify the domain that this material will operate in. - MaterialBuilder& materialDomain(MaterialBuilder::MaterialDomain materialDomain) noexcept; - - /** - * Set the code content of this material. - * - * Surface Domain - * -------------- - * - * Materials in the SURFACE domain must declare a function: - * ~~~~~ - * void material(inout MaterialInputs material) { - * prepareMaterial(material); - * material.baseColor.rgb = float3(1.0, 0.0, 0.0); - * } - * ~~~~~ - * this function *must* call `prepareMaterial(material)` before it returns. - * - * Post-process Domain - * ------------------- - * - * Materials in the POST_PROCESS domain must declare a function: - * ~~~~~ - * void postProcess(inout PostProcessInputs postProcess) { - * postProcess.color = float4(1.0); - * } - * ~~~~~ - * - * @param code The source code of the material. - * @param line The line number offset of the material, where 0 is the first line. Used for error - * reporting - */ - MaterialBuilder& material(const char* code, size_t line = 0) noexcept; - - /** - * Set the callback used for resolving include directives. - * The default is no callback, which disallows all includes. - */ - MaterialBuilder& includeCallback(IncludeCallback callback) noexcept; - - /** - * Set the vertex code content of this material. - * - * Surface Domain - * -------------- - * - * Materials in the SURFACE domain must declare a function: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * void materialVertex(inout MaterialVertexInputs material) { - * - * } - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Post-process Domain - * ------------------- - * - * Materials in the POST_PROCESS domain must declare a function: - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * void postProcessVertex(inout PostProcessVertexInputs postProcess) { - * - * } - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - * @param code The source code of the material. - * @param line The line number offset of the material, where 0 is the first line. Used for error - * reporting - */ - MaterialBuilder& materialVertex(const char* code, size_t line = 0) noexcept; - - - MaterialBuilder& quality(ShaderQuality quality) noexcept; - - MaterialBuilder& featureLevel(FeatureLevel featureLevel) noexcept; - - /** - * Set the blending mode for this material. When set to MASKED, alpha to coverage is turned on. - * You can override this behavior using alphaToCoverage(false). - */ - MaterialBuilder& blending(BlendingMode blending) noexcept; - - /** - * Set the blend function for this material. blending must be et to CUSTOM. - */ - MaterialBuilder& customBlendFunctions( - BlendFunction srcRGB, - BlendFunction srcA, - BlendFunction dstRGB, - BlendFunction dstA) noexcept; - - /** - * Set the blending mode of the post-lighting color for this material. - * Only OPAQUE, TRANSPARENT and ADD are supported, the default is TRANSPARENT. - * This setting requires the material properties "postLightingColor" and - * "postLightingMixFactor" to be set. - */ - MaterialBuilder& postLightingBlending(BlendingMode blending) noexcept; - - //! Set the vertex domain for this material. - MaterialBuilder& vertexDomain(VertexDomain domain) noexcept; - - /** - * How triangles are culled by default (doesn't affect points or lines, BACK by default). - * Material instances can override this. - */ - MaterialBuilder& culling(CullingMode culling) noexcept; - - //! Enable / disable color-buffer write (enabled by default, material instances can override). - MaterialBuilder& colorWrite(bool enable) noexcept; - - //! Enable / disable depth-buffer write (enabled by default for opaque, disabled for others, material instances can override). - MaterialBuilder& depthWrite(bool enable) noexcept; - - //! Enable / disable depth based culling (enabled by default, material instances can override). - MaterialBuilder& depthCulling(bool enable) noexcept; - - //! Enable / disable instanced primitives (disabled by default). - MaterialBuilder& instanced(bool enable) noexcept; - - /** - * Double-sided materials don't cull faces, equivalent to culling(CullingMode::NONE). - * doubleSided() overrides culling() if called. - * When called with "false", this enables the capability for a run-time toggle. - */ - MaterialBuilder& doubleSided(bool doubleSided) noexcept; - - /** - * Any fragment with an alpha below this threshold is clipped (MASKED blending mode only). - * The mask threshold can also be controlled by using the float material parameter called - * `_maskThreshold`, or by calling - * @ref filament::MaterialInstance::setMaskThreshold "MaterialInstance::setMaskThreshold". - */ - MaterialBuilder& maskThreshold(float threshold) noexcept; - - /** - * Enables or disables alpha-to-coverage. When enabled, the coverage of a fragment is based - * on its alpha value. This parameter is only useful when MSAA is in use. Alpha to coverage - * is enabled automatically when the blend mode is set to MASKED; this behavior can be - * overridden by calling alphaToCoverage(false). - */ - MaterialBuilder& alphaToCoverage(bool enable) noexcept; - - //! The material output is multiplied by the shadowing factor (UNLIT model only). - MaterialBuilder& shadowMultiplier(bool shadowMultiplier) noexcept; - - //! This material casts transparent shadows. The blending mode must be TRANSPARENT or FADE. - MaterialBuilder& transparentShadow(bool transparentShadow) noexcept; - - /** - * Reduces specular aliasing for materials that have low roughness. Turning this feature on also - * helps preserve the shapes of specular highlights as an object moves away from the camera. - * When turned on, two float material parameters are added to control the effect: - * `_specularAAScreenSpaceVariance` and `_specularAAThreshold`. You can also use - * @ref filament::MaterialInstance::setSpecularAntiAliasingVariance - * "MaterialInstance::setSpecularAntiAliasingVariance" and - * @ref filament::MaterialInstance::setSpecularAntiAliasingThreshold - * "setSpecularAntiAliasingThreshold" - * - * Disabled by default. - */ - MaterialBuilder& specularAntiAliasing(bool specularAntiAliasing) noexcept; - - /** - * Sets the screen-space variance of the filter kernel used when applying specular - * anti-aliasing. The default value is set to 0.15. The specified value should be between 0 and - * 1 and will be clamped if necessary. - */ - MaterialBuilder& specularAntiAliasingVariance(float screenSpaceVariance) noexcept; - - /** - * Sets the clamping threshold used to suppress estimation errors when applying specular - * anti-aliasing. The default value is set to 0.2. The specified value should be between 0 and 1 - * and will be clamped if necessary. - */ - MaterialBuilder& specularAntiAliasingThreshold(float threshold) noexcept; - - /** - * Enables or disables the index of refraction (IoR) change caused by the clear coat layer when - * present. When the IoR changes, the base color is darkened. Disabling this feature preserves - * the base color as initially specified. - * - * Enabled by default. - */ - MaterialBuilder& clearCoatIorChange(bool clearCoatIorChange) noexcept; - - //! Enable / disable flipping of the Y coordinate of UV attributes, enabled by default. - MaterialBuilder& flipUV(bool flipUV) noexcept; - - //! Enable / disable multi-bounce ambient occlusion, disabled by default on mobile. - MaterialBuilder& multiBounceAmbientOcclusion(bool multiBounceAO) noexcept; - - //! Set the specular ambient occlusion technique. Disabled by default on mobile. - MaterialBuilder& specularAmbientOcclusion(SpecularAmbientOcclusion specularAO) noexcept; - - //! Specify the refraction - MaterialBuilder& refractionMode(RefractionMode refraction) noexcept; - - //! Specify the refraction type - MaterialBuilder& refractionType(RefractionType refractionType) noexcept; - - //! Specifies how reflections should be rendered (default is DEFAULT). - MaterialBuilder& reflectionMode(ReflectionMode mode) noexcept; - - //! Specifies how transparent objects should be rendered (default is DEFAULT). - MaterialBuilder& transparencyMode(TransparencyMode mode) noexcept; - - //! Specify the stereoscopic type (default is INSTANCED) - MaterialBuilder& stereoscopicType(StereoscopicType stereoscopicType) noexcept; - - //! Specify the number of eyes for stereoscopic rendering - MaterialBuilder& stereoscopicEyeCount(uint8_t eyeCount) noexcept; - - /** - * Enable / disable custom surface shading. Custom surface shading requires the LIT - * shading model. In addition, the following function must be defined in the fragment - * block: - * - * ~~~~~ - * vec3 surfaceShading(const MaterialInputs materialInputs, - * const ShadingData shadingData, const LightData lightData) { - * - * return vec3(1.0); // Compute surface shading with custom BRDF, etc. - * } - * ~~~~~ - * - * This function is invoked once per light. Please refer to the materials documentation - * for more information about the different parameters. - * - * @param customSurfaceShading Enables or disables custom surface shading - */ - MaterialBuilder& customSurfaceShading(bool customSurfaceShading) noexcept; - - /** - * Specifies desktop vs mobile; works in concert with TargetApi to determine the shader models - * (used to generate code) and final output representations (spirv and/or text). - */ - MaterialBuilder& platform(Platform platform) noexcept; - - /** - * Specifies OpenGL, Vulkan, or Metal. - * This can be called repeatedly to build for multiple APIs. - * Works in concert with Platform to determine the shader models (used to generate code) and - * final output representations (spirv and/or text). - * If linking against filamat_lite, only `OPENGL` is allowed. - */ - MaterialBuilder& targetApi(TargetApi targetApi) noexcept; - - /** - * Specifies the level of optimization to apply to the shaders (default is PERFORMANCE). - * If linking against filamat_lite, this _must_ be called with Optimization::NONE. - */ - MaterialBuilder& optimization(Optimization optimization) noexcept; - - // TODO: this is present here for matc's "--print" flag, but ideally does not belong inside - // MaterialBuilder. - //! If true, will output the generated GLSL shader code to stdout. - MaterialBuilder& printShaders(bool printShaders) noexcept; - - //! If true, will include debugging information in generated SPIRV. - MaterialBuilder& generateDebugInfo(bool generateDebugInfo) noexcept; - - //! Specifies a list of variants that should be filtered out during code generation. - MaterialBuilder& variantFilter(filament::UserVariantFilterMask variantFilter) noexcept; - - //! Adds a new preprocessor macro definition to the shader code. Can be called repeatedly. - MaterialBuilder& shaderDefine(const char* name, const char* value) noexcept; - - //! Add a new fragment shader output variable. Only valid for materials in the POST_PROCESS domain. - MaterialBuilder& output(VariableQualifier qualifier, OutputTarget target, Precision precision, - OutputType type, const char* name, int location = -1) noexcept; - - MaterialBuilder& enableFramebufferFetch() noexcept; - - MaterialBuilder& vertexDomainDeviceJittered(bool enabled) noexcept; - - /** - * Legacy morphing uses the data in the VertexAttribute slots (\c MORPH_POSITION_0, etc) and is - * limited to 4 morph targets. See filament::RenderableManager::Builder::morphing(). - */ - MaterialBuilder& useLegacyMorphing() noexcept; - - //! specify compute kernel group size - MaterialBuilder& groupSize(filament::math::uint3 groupSize) noexcept; - - /** - * Build the material. If you are using the Filament engine with this library, you should use - * the job system provided by Engine. - */ - Package build(utils::JobSystem& jobSystem) noexcept; - -public: - // The methods and types below are for internal use - /// @cond never - - /** - * Add a subpass parameter to this material. - */ - MaterialBuilder& subpass(SubpassType subpassType, - SamplerFormat format, ParameterPrecision precision, const char* name) noexcept; - MaterialBuilder& subpass(SubpassType subpassType, - SamplerFormat format, const char* name) noexcept; - MaterialBuilder& subpass(SubpassType subpassType, - ParameterPrecision precision, const char* name) noexcept; - MaterialBuilder& subpass(SubpassType subpassType, const char* name) noexcept; - - struct Parameter { - Parameter() noexcept: parameterType(INVALID) {} - - // Sampler - Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p, bool ms) - : name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER), multisample(ms) { } - - // Uniform - Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p) - : name(paramName), size(typeSize), uniformType(t), precision(p), parameterType(UNIFORM) { } - - // Subpass - Parameter(const char* paramName, SubpassType t, SamplerFormat f, ParameterPrecision p) - : name(paramName), size(1), precision(p), subpassType(t), format(f), parameterType(SUBPASS) { } - - utils::CString name; - size_t size; - UniformType uniformType; - ParameterPrecision precision; - SamplerType samplerType; - SubpassType subpassType; - SamplerFormat format; - bool multisample; - enum { - INVALID, - UNIFORM, - SAMPLER, - SUBPASS - } parameterType; - - bool isSampler() const { return parameterType == SAMPLER; } - bool isUniform() const { return parameterType == UNIFORM; } - bool isSubpass() const { return parameterType == SUBPASS; } - }; - - struct Output { - Output() noexcept = default; - Output(const char* outputName, VariableQualifier qualifier, OutputTarget target, - Precision precision, OutputType type, int location) noexcept - : name(outputName), qualifier(qualifier), target(target), precision(precision), - type(type), location(location) { } - - utils::CString name; - VariableQualifier qualifier; - OutputTarget target; - Precision precision; - OutputType type; - int location; - }; - - struct Constant { - utils::CString name; - ConstantType type; - union { - int32_t i; - float f; - bool b; - } defaultValue; - }; - - struct PushConstant { - utils::CString name; - ConstantType type; - ShaderStage stage; - }; - - static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT; - using Property = filament::Property; - - using PropertyList = bool[MATERIAL_PROPERTIES_COUNT]; - using VariableList = utils::CString[MATERIAL_VARIABLES_COUNT]; - using OutputList = std::vector; - - static constexpr size_t MAX_COLOR_OUTPUT = filament::backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; - static constexpr size_t MAX_DEPTH_OUTPUT = 1; - static_assert(MAX_COLOR_OUTPUT == 8, - "When updating MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, manually update post_process_inputs.fs" - " and post_process.fs"); - - // Preview the first shader generated by the given CodeGenParams. - // This is used to run Static Code Analysis before generating a package. - std::string peek(filament::backend::ShaderStage type, - const CodeGenParams& params, const PropertyList& properties) noexcept; - - // Returns true if any of the parameter samplers matches the specified type. - bool hasSamplerType(SamplerType samplerType) const noexcept; - - static constexpr size_t MAX_PARAMETERS_COUNT = 48; - static constexpr size_t MAX_SUBPASS_COUNT = 1; - static constexpr size_t MAX_BUFFERS_COUNT = 4; - using ParameterList = Parameter[MAX_PARAMETERS_COUNT]; - using SubpassList = Parameter[MAX_SUBPASS_COUNT]; - using BufferList = std::vector>; - using ConstantList = std::vector; - using PushConstantList = std::vector; - - // returns the number of parameters declared in this material - uint8_t getParameterCount() const noexcept { return mParameterCount; } - - // returns a list of at least getParameterCount() parameters - const ParameterList& getParameters() const noexcept { return mParameters; } - - // returns the number of parameters declared in this material - uint8_t getSubpassCount() const noexcept { return mSubpassCount; } - - // returns a list of at least getParameterCount() parameters - const SubpassList& getSubPasses() const noexcept { return mSubpasses; } - - filament::UserVariantFilterMask getVariantFilter() const { return mVariantFilter; } - - FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } - /// @endcond - - struct Attribute { - std::string_view name; - AttributeType type; - MaterialBuilder::VertexAttribute location; - std::string getAttributeName() const noexcept { - return "mesh_" + std::string{ name }; - } - std::string getDefineName() const noexcept { - std::string uppercase{ name }; - transform(uppercase.cbegin(), uppercase.cend(), uppercase.begin(), ::toupper); - return "HAS_ATTRIBUTE_" + uppercase; - } - }; - - using AttributeDatabase = std::array; - - static inline AttributeDatabase const& getAttributeDatabase() noexcept { - return sAttributeDatabase; - } - -private: - static const AttributeDatabase sAttributeDatabase; - - void prepareToBuild(MaterialInfo& info) noexcept; - - // Initialize internal push constants that will both be written to the shaders and material - // chunks (like user-defined spec constants). - void initPushConstants() noexcept; - - // Return true if the shader is syntactically and semantically valid. - // This method finds all the properties defined in the fragment and - // vertex shaders of the material. - bool findAllProperties(CodeGenParams const& semanticCodeGenParams) noexcept; - - // Multiple calls to findProperties accumulate the property sets across fragment - // and vertex shaders in mProperties. - bool findProperties(filament::backend::ShaderStage type, - MaterialBuilder::PropertyList& allProperties, - CodeGenParams const& semanticCodeGenParams) noexcept; - - bool runSemanticAnalysis(MaterialInfo* inOutInfo, - CodeGenParams const& semanticCodeGenParams) noexcept; - - bool checkLiteRequirements() noexcept; - - bool checkMaterialLevelFeatures(MaterialInfo const& info) const noexcept; - - void writeCommonChunks(ChunkContainer& container, MaterialInfo& info) const noexcept; - void writeSurfaceChunks(ChunkContainer& container) const noexcept; - - bool generateShaders( - utils::JobSystem& jobSystem, - const std::vector& variants, ChunkContainer& container, - const MaterialInfo& info) const noexcept; - - bool hasCustomVaryings() const noexcept; - bool needsStandardDepthProgram() const noexcept; - - bool isLit() const noexcept { return mShading != filament::Shading::UNLIT; } - - utils::CString mMaterialName; - utils::CString mFileName; - - class ShaderCode { - public: - void setLineOffset(size_t offset) noexcept { mLineOffset = offset; } - void setUnresolved(const utils::CString& code) noexcept { - mIncludesResolved = false; - mCode = code; - } - - // Resolve all the #include directives, returns true if successful. - bool resolveIncludes(IncludeCallback callback, const utils::CString& fileName) noexcept; - - const utils::CString& getResolved() const noexcept { - assert(mIncludesResolved); - return mCode; - } - - size_t getLineOffset() const noexcept { return mLineOffset; } - - private: - utils::CString mCode; - size_t mLineOffset = 0; - bool mIncludesResolved = false; - }; - - ShaderCode mMaterialFragmentCode; - ShaderCode mMaterialVertexCode; - - IncludeCallback mIncludeCallback = nullptr; - - PropertyList mProperties; - ParameterList mParameters; - ConstantList mConstants; - PushConstantList mPushConstants; - SubpassList mSubpasses; - VariableList mVariables; - OutputList mOutputs; - BufferList mBuffers; - - ShaderQuality mShaderQuality = ShaderQuality::DEFAULT; - FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; - BlendingMode mBlendingMode = BlendingMode::OPAQUE; - BlendingMode mPostLightingBlendingMode = BlendingMode::TRANSPARENT; - std::array mCustomBlendFunctions = {}; - CullingMode mCullingMode = CullingMode::BACK; - Shading mShading = Shading::LIT; - MaterialDomain mMaterialDomain = MaterialDomain::SURFACE; - RefractionMode mRefractionMode = RefractionMode::NONE; - RefractionType mRefractionType = RefractionType::SOLID; - ReflectionMode mReflectionMode = ReflectionMode::DEFAULT; - Interpolation mInterpolation = Interpolation::SMOOTH; - VertexDomain mVertexDomain = VertexDomain::OBJECT; - TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; - StereoscopicType mStereoscopicType = StereoscopicType::INSTANCED; - uint8_t mStereoscopicEyeCount = 2; - - filament::AttributeBitset mRequiredAttributes; - - float mMaskThreshold = 0.4f; - float mSpecularAntiAliasingVariance = 0.15f; - float mSpecularAntiAliasingThreshold = 0.2f; - - filament::math::uint3 mGroupSize = { 1, 1, 1 }; - - bool mShadowMultiplier = false; - bool mTransparentShadow = false; - - uint8_t mParameterCount = 0; - uint8_t mSubpassCount = 0; - - bool mDoubleSided = false; - bool mDoubleSidedCapability = false; - bool mColorWrite = true; - bool mDepthTest = true; - bool mInstanced = false; - bool mDepthWrite = true; - bool mDepthWriteSet = false; - bool mAlphaToCoverage = false; - bool mAlphaToCoverageSet = false; - - bool mSpecularAntiAliasing = false; - bool mClearCoatIorChange = true; - - bool mFlipUV = true; - - bool mMultiBounceAO = false; - bool mMultiBounceAOSet = false; - - SpecularAmbientOcclusion mSpecularAO = SpecularAmbientOcclusion::NONE; - bool mSpecularAOSet = false; - - bool mCustomSurfaceShading = false; - - bool mEnableFramebufferFetch = false; - - bool mVertexDomainDeviceJittered = false; - - bool mUseLegacyMorphing = false; - - PreprocessorDefineList mDefines; - - filament::UserVariantFilterMask mVariantFilter = {}; - - bool mNoSamplerValidation = false; -}; - -} // namespace filamat - -template<> struct utils::EnableBitMaskOperators - : public std::true_type {}; - -#endif diff --git a/package/ios/libs/filament/include/filamat/Package.h b/package/ios/libs/filament/include/filamat/Package.h deleted file mode 100644 index 93e74a58..00000000 --- a/package/ios/libs/filament/include/filamat/Package.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_FILAMAT_PACKAGE_H -#define TNT_FILAMAT_PACKAGE_H - -#include -#include -#include - -#include -#include - -#include - -namespace filamat { - -class UTILS_PUBLIC Package { -public: - Package() = default; - - // Regular constructor - explicit Package(size_t size) : mSize(size) { - mPayload = new uint8_t[size]; - } - - Package(const void* src, size_t size) : Package(size) { - memcpy(mPayload, src, size); - } - - // Move Constructor - Package(Package&& other) noexcept : mPayload(other.mPayload), mSize(other.mSize), - mValid(other.mValid) { - other.mPayload = nullptr; - other.mSize = 0; - other.mValid = false; - } - - // Move assignment - Package& operator=(Package&& other) noexcept { - std::swap(mPayload, other.mPayload); - std::swap(mSize, other.mSize); - std::swap(mValid, other.mValid); - return *this; - } - - // Copy assignment operator disallowed. - Package& operator=(const Package& other) = delete; - - // Copy constructor disallowed. - Package(const Package& other) = delete; - - ~Package() { - delete[] mPayload; - } - - uint8_t* getData() const noexcept { - return mPayload; - } - - size_t getSize() const noexcept { - return mSize; - } - - uint8_t* getEnd() const noexcept { - return mPayload + mSize; - } - - void setValid(bool valid) noexcept { - mValid = valid; - } - - bool isValid() const noexcept { - return mValid; - } - - static Package invalidPackage() { - Package package(0); - package.setValid(false); - return package; - } - -private: - uint8_t* mPayload = nullptr; - size_t mSize = 0; - bool mValid = true; -}; - -} // namespace filamat -#endif diff --git a/package/ios/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h b/package/ios/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h new file mode 100644 index 00000000..ab506c9f --- /dev/null +++ b/package/ios/libs/filament/include/filament-generatePrefilterMipmap/generatePrefilterMipmap.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +namespace filament { + +namespace backend { +class PixelBufferDescriptor; +} + +class Engine; +class Texture; + +struct UTILS_PUBLIC FaceOffsets { + using size_type = size_t; + union { + struct { + size_type px; //!< +x face offset in bytes + size_type nx; //!< -x face offset in bytes + size_type py; //!< +y face offset in bytes + size_type ny; //!< -y face offset in bytes + size_type pz; //!< +z face offset in bytes + size_type nz; //!< -z face offset in bytes + }; + size_type offsets[6]; + }; + size_type operator[](size_t const n) const noexcept { return offsets[n]; } + size_type& operator[](size_t const n) { return offsets[n]; } + FaceOffsets() noexcept = default; + explicit FaceOffsets(size_type const faceSize) noexcept { + px = faceSize * 0; + nx = faceSize * 1; + py = faceSize * 2; + ny = faceSize * 3; + pz = faceSize * 4; + nz = faceSize * 5; + } + FaceOffsets(const FaceOffsets& rhs) noexcept { + px = rhs.px; + nx = rhs.nx; + py = rhs.py; + ny = rhs.ny; + pz = rhs.pz; + nz = rhs.nz; + } + FaceOffsets& operator=(const FaceOffsets& rhs) noexcept { + px = rhs.px; + nx = rhs.nx; + py = rhs.py; + ny = rhs.ny; + pz = rhs.pz; + nz = rhs.nz; + return *this; + } +}; + +/** + * Options for environment prefiltering into reflection map + * + * @see generatePrefilterMipmap() + */ +struct UTILS_PUBLIC PrefilterOptions { + uint16_t sampleCount = 8; //!< sample count used for filtering + bool mirror = true; //!< whether the environment must be mirrored +private: + UTILS_UNUSED uintptr_t reserved[3] = {}; +}; + +UTILS_PUBLIC +void generatePrefilterMipmap(Texture* UTILS_NONNULL texture, Engine& engine, + backend::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, + PrefilterOptions const* UTILS_NULLABLE options = nullptr); + + +} // namespace filament diff --git a/package/ios/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h b/package/ios/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h index 903c4258..e894e148 100644 --- a/package/ios/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h +++ b/package/ios/libs/filament/include/filament-iblprefilter/IBLPrefilterContext.h @@ -227,7 +227,6 @@ class UTILS_PUBLIC IBLPrefilterContext { filament::Texture* outIrradianceTexture = nullptr); private: - filament::Texture* createIrradianceTexture(); IBLPrefilterContext& mContext; filament::Material* mKernelMaterial = nullptr; filament::Texture* mKernelTexture = nullptr; @@ -325,7 +324,6 @@ class UTILS_PUBLIC IBLPrefilterContext { // TODO: add a callback for when the processing is done? private: - filament::Texture* createReflectionsTexture(); IBLPrefilterContext& mContext; filament::Material* mKernelMaterial = nullptr; filament::Texture* mKernelTexture = nullptr; diff --git a/package/ios/libs/filament/include/filament/Box.h b/package/ios/libs/filament/include/filament/Box.h index da6638da..35042030 100644 --- a/package/ios/libs/filament/include/filament/Box.h +++ b/package/ios/libs/filament/include/filament/Box.h @@ -120,8 +120,11 @@ class UTILS_PUBLIC Box { } /** - * @deprecated Use transform() instead - * @see transform() + * Transform a Box by a linear transform and a translation. + * + * @param m a linear transform matrix + * @param box the box to transform + * @return the bounding box of the transformed box */ friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept { return transform(m.upperLeft(), m[3].xyz, box); @@ -183,7 +186,7 @@ struct UTILS_PUBLIC Aabb { * Returns the 8 corner vertices of the AABB. */ Corners getCorners() const { - return Aabb::Corners{ .vertices = { + return Corners{ .vertices = { { min.x, min.y, min.z }, { max.x, min.y, min.z }, { min.x, max.y, min.z }, @@ -235,8 +238,10 @@ struct UTILS_PUBLIC Aabb { } /** - * @deprecated Use transform() instead - * @see transform() + * Applies an affine transformation to the AABB. + * + * @param m the affine transformation to apply + * @return the bounding box of the transformed box */ Aabb transform(const math::mat4f& m) const noexcept { return transform(m.upperLeft(), m[3].xyz, *this); diff --git a/package/ios/libs/filament/include/filament/BufferObject.h b/package/ios/libs/filament/include/filament/BufferObject.h index 74a4b1ff..bf3ffe3d 100644 --- a/package/ios/libs/filament/include/filament/BufferObject.h +++ b/package/ios/libs/filament/include/filament/BufferObject.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -54,7 +55,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { using BufferDescriptor = backend::BufferDescriptor; using BindingType = backend::BufferObjectBinding; - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -73,11 +74,38 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { /** * The binding type for this buffer object. (defaults to VERTEX) - * @param BindingType Distinguishes between SSBO, VBO, etc. For now this must be VERTEX. + * @param bindingType Distinguishes between SSBO, VBO, etc. For now this must be VERTEX. * @return A reference to this Builder for chaining calls. */ Builder& bindingType(BindingType bindingType) noexcept; + /** + * Associate an optional name with this BufferObject for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this BufferObject + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this BufferObject for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this BufferObject + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the BufferObject and returns a pointer to it. After creation, the buffer * object is uninitialized. Use BufferObject::setBuffer() to initialize it. @@ -102,7 +130,7 @@ class UTILS_PUBLIC BufferObject : public FilamentAPI { * * @param engine Reference to the filament::Engine associated with this BufferObject. * @param buffer A BufferDescriptor representing the data used to initialize the BufferObject. - * @param byteOffset Offset in bytes into the BufferObject + * @param byteOffset Offset in bytes into the BufferObject. Must be multiple of 4. */ void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0); diff --git a/package/ios/libs/filament/include/filament/Camera.h b/package/ios/libs/filament/include/filament/Camera.h index 74f34af0..abf8b2c1 100644 --- a/package/ios/libs/filament/include/filament/Camera.h +++ b/package/ios/libs/filament/include/filament/Camera.h @@ -61,7 +61,7 @@ namespace filament { * filament::Camera* myCamera = engine->createCamera(myCameraEntity); * myCamera->setProjection(45, 16.0/9.0, 0.1, 1.0); * myCamera->lookAt({0, 1.60, 1}, {0, 0, 0}); - * engine->destroyCameraComponent(myCamera); + * engine->destroyCameraComponent(myCameraEntity); * ~~~~~~~~~~~ * * @@ -243,10 +243,10 @@ class UTILS_PUBLIC Camera : public FilamentAPI { /** Utility to set the projection matrix from the field-of-view. * * @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180. - * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. - * @param direction direction of the \p fovInDegrees parameter. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param direction direction of the \p fovInDegrees parameter. * * @see Fov. */ @@ -256,9 +256,9 @@ class UTILS_PUBLIC Camera : public FilamentAPI { /** Utility to set the projection matrix from the focal length. * * @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0. - * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0. + * @param near distance in world units from the camera to the near plane. \p near > 0. + * @param far distance in world units from the camera to the far plane. \p far > \p near. */ void setLensProjection(double focalLengthInMillimeters, double aspect, double near, double far); @@ -270,8 +270,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * that is all 3 axis are mapped to [-1, 1]. * * @param projection custom projection matrix used for rendering and culling - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param near distance in world units from the camera to the near plane. + * @param far distance in world units from the camera to the far plane. \p far != \p near. */ void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; @@ -282,8 +282,8 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * * @param projection custom projection matrix used for rendering * @param projectionForCulling custom projection matrix used for culling - * @param near distance in world units from the camera to the near plane. \p near > 0. - * @param far distance in world units from the camera to the far plane. \p far > \p near. + * @param near distance in world units from the camera to the near plane. + * @param far distance in world units from the camera to the far plane. \p far != \p near. */ void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, double near, double far) noexcept; @@ -400,14 +400,14 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * engine.getTransformManager().getInstance(camera->getEntity()), model); * ~~~~~~~~~~~ * - * @param model The camera position and orientation provided as a rigid transform matrix. + * @param modelMatrix The camera position and orientation provided as a rigid transform matrix. * * @note The Camera "looks" towards its -z axis * * @warning \p model must be a rigid transform */ - void setModelMatrix(const math::mat4& model) noexcept; - void setModelMatrix(const math::mat4f& model) noexcept; //!< @overload + void setModelMatrix(const math::mat4& modelMatrix) noexcept; + void setModelMatrix(const math::mat4f& modelMatrix) noexcept; //!< @overload /** Set the position of an eye relative to this Camera (head). * diff --git a/package/ios/libs/filament/include/filament/Color.h b/package/ios/libs/filament/include/filament/Color.h index 30b77856..37939820 100644 --- a/package/ios/libs/filament/include/filament/Color.h +++ b/package/ios/libs/filament/include/filament/Color.h @@ -194,7 +194,7 @@ inline sRGBColorA Color::toSRGB(LinearColorA const& color) { } inline LinearColor Color::toLinear(RgbType type, math::float3 color) { - return (type == RgbType::LINEAR) ? color : Color::toLinear(color); + return (type == RgbType::LINEAR) ? color : toLinear(color); } // converts an RGBA color to linear space @@ -202,11 +202,11 @@ inline LinearColor Color::toLinear(RgbType type, math::float3 color) { inline LinearColorA Color::toLinear(RgbaType type, math::float4 color) { switch (type) { case RgbaType::sRGB: - return Color::toLinear(color) * math::float4{color.a, color.a, color.a, 1.0f}; + return toLinear(color) * math::float4{color.a, color.a, color.a, 1.0f}; case RgbaType::LINEAR: return color * math::float4{color.a, color.a, color.a, 1.0f}; case RgbaType::PREMULTIPLIED_sRGB: - return Color::toLinear(color); + return toLinear(color); case RgbaType::PREMULTIPLIED_LINEAR: return color; } diff --git a/package/ios/libs/filament/include/filament/ColorGrading.h b/package/ios/libs/filament/include/filament/ColorGrading.h index e5c8f3ca..3e4916fb 100644 --- a/package/ios/libs/filament/include/filament/ColorGrading.h +++ b/package/ios/libs/filament/include/filament/ColorGrading.h @@ -64,9 +64,8 @@ class ColorSpace; * Performance * =========== * - * Creating a new ColorGrading object may be more expensive than other Filament objects as a - * 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on - * the CPU. + * Creating a new ColorGrading object may be more expensive than other Filament objects as a LUT may + * need to be generated. The generation of this LUT, if necessary, may happen on the CPU. * * Ordering * ======== @@ -155,6 +154,9 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * 3D texture. For instance, a low quality level will use a 16x16x16 10 bit LUT, a medium * quality level will use a 32x32x32 10 bit LUT, a high quality will use a 32x32x32 16 bit * LUT, and a ultra quality will use a 64x64x64 16 bit LUT. + * + * This setting has no effect if generating a 1D LUT. + * * This overrides the values set by format() and dimensions(). * * The default quality is medium. @@ -169,6 +171,8 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * When color grading is implemented using a 3D LUT, this sets the texture format of * of the LUT. This overrides the value set by quality(). * + * This setting has no effect if generating a 1D LUT. + * * The default is INTEGER * * @param format The desired format of the 3D LUT. @@ -181,6 +185,8 @@ class UTILS_PUBLIC ColorGrading : public FilamentAPI { * When color grading is implemented using a 3D LUT, this sets the dimension of the LUT. * This overrides the value set by quality(). * + * This setting has no effect if generating a 1D LUT. + * * The default is 32 * * @param dim The desired dimension of the LUT. Between 16 and 64. diff --git a/package/ios/libs/filament/include/filament/Engine.h b/package/ios/libs/filament/include/filament/Engine.h index 2aed8273..c9dee81c 100644 --- a/package/ios/libs/filament/include/filament/Engine.h +++ b/package/ios/libs/filament/include/filament/Engine.h @@ -17,6 +17,7 @@ #ifndef TNT_FILAMENT_ENGINE_H #define TNT_FILAMENT_ENGINE_H + #include #include @@ -24,10 +25,16 @@ #include #include +#include + +#include +#include +#include #include #include + namespace utils { class Entity; class EntityManager; @@ -36,6 +43,10 @@ class JobSystem; namespace filament { +namespace backend { +class Driver; +} // backend + class BufferObject; class Camera; class ColorGrading; @@ -53,6 +64,7 @@ class Scene; class Skybox; class Stream; class SwapChain; +class Sync; class Texture; class VertexBuffer; class View; @@ -179,6 +191,11 @@ class UTILS_PUBLIC Engine { using DriverConfig = backend::Platform::DriverConfig; using FeatureLevel = backend::FeatureLevel; using StereoscopicType = backend::StereoscopicType; + using Driver = backend::Driver; + using GpuContextPriority = backend::Platform::GpuContextPriority; + using AsynchronousMode = backend::AsynchronousMode; + using AsyncCompletionCallback = std::function; + using AsyncCallId = backend::AsyncCallId; /** * Config is used to define the memory footprint used by the engine, such as the @@ -288,19 +305,37 @@ class UTILS_PUBLIC Engine { */ uint32_t jobSystemThreadCount = 0; - /* - * Number of most-recently destroyed textures to track for use-after-free. + /** + * When uploading vertex or index data, the Filament Metal backend copies data + * into a shared staging area before transferring it to the GPU. This setting controls + * the total size of the buffer used to perform these allocations. + * + * Higher values can improve performance when performing many uploads across a small + * number of frames. * - * This will cause the backend to throw an exception when a texture is freed but still bound - * to a SamplerGroup and used in a draw call. 0 disables completely. + * This buffer remains alive throughout the lifetime of the Engine, so this size adds to the + * memory footprint of the app and should be set as conservative as possible. * - * Currently only respected by the Metal backend. + * A value of 0 disables the shared staging buffer entirely; uploads will acquire an + * individual buffer from a pool of shared buffers. + * + * Only respected by the Metal backend. + */ + size_t metalUploadBufferSizeBytes = 512 * 1024; + + /** + * The action to take if a Drawable cannot be acquired. + * + * Each frame rendered requires a CAMetalDrawable texture, which is + * presented on-screen at the completion of each frame. These are + * limited and provided round-robin style by the system. */ - size_t textureUseAfterFreePoolSize = 0; + bool metalDisablePanicOnDrawableFailure = false; /** * Set to `true` to forcibly disable parallel shader compilation in the backend. * Currently only honored by the GL and Metal backends. + * @deprecated use "backend.disable_parallel_shader_compile" feature flag instead */ bool disableParallelShaderCompile = false; @@ -332,12 +367,16 @@ class UTILS_PUBLIC Engine { uint32_t resourceAllocatorCacheSizeMB = 64; /* - * This value determines for how many frames are texture entries kept in the cache. + * This value determines how many frames texture entries are kept for in the cache. This + * is a soft limit, meaning some texture older than this are allowed to stay in the cache. + * Typically only one texture is evicted per frame. + * The default is 1. */ - uint32_t resourceAllocatorCacheMaxAge = 2; + uint32_t resourceAllocatorCacheMaxAge = 1; /* * Disable backend handles use-after-free checks. + * @deprecated use "backend.disable_handle_use_after_free_check" feature flag instead */ bool disableHandleUseAfterFreeCheck = false; @@ -369,9 +408,62 @@ class UTILS_PUBLIC Engine { * it's a GLES2 context. Ignored on other backends. */ bool forceGLES2Context = false; + + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + * @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead + */ + bool assertNativeWindowIsValid = false; + + /** + * GPU context priority level. Controls GPU work scheduling and preemption. + */ + GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT; + + /** + * The initial size in bytes of the shared uniform buffer used for material instance + * batching. + * + * If the buffer runs out of space during a frame, it will be automatically reallocated + * with a larger capacity. Setting an appropriate initial size can help avoid runtime + * reallocations, which can cause a minor performance stutter, at the cost of higher + * initial memory usage. + */ + uint32_t sharedUboInitialSizeInBytes = 256 * 64; + + /** + * Asynchronous mode for the engine. Defines how asynchronous operations are handled. + * Note that selecting a non-NONE mode does not guarantee asynchronous methods are + * supported, as the underlying backend or the feature flag may override this configuration. + * Always validate availability via Engine::isAsynchronousModeEnabled() before + * invoking asynchronous methods. + */ + AsynchronousMode asynchronousMode = AsynchronousMode::NONE; }; + /** + * Feature flags can be enabled or disabled when the Engine is built. Some Feature flags can + * also be toggled at any time. Feature flags should alawys use their default value unless + * the feature enabled by the flag is faulty. Feature flags provide a last resort way to + * disable problematic features. + * Feature flags are intended to have a short life-time and are regularly removed as features + * mature. + */ + struct FeatureFlag { + char const* UTILS_NONNULL name; //!< name of the feature flag + char const* UTILS_NONNULL description; //!< short description + bool const* UTILS_NONNULL value; //!< pointer to the value of the flag + bool constant = true; //!< whether the flag is constant after construction + }; + + /** + * Returns the list of available feature flags + */ + utils::Slice getFeatureFlags() const noexcept; + #if UTILS_HAS_THREADING using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token); #endif @@ -444,6 +536,21 @@ class UTILS_PUBLIC Engine { */ Builder& paused(bool paused) noexcept; + /** + * Set a feature flag value. This is the only way to set constant feature flags. + * @param name feature name + * @param value true to enable, false to disable + * @return A reference to this Builder for chaining calls. + */ + Builder& feature(char const* UTILS_NONNULL name, bool value) noexcept; + + /** + * Enables a list of features. + * @param list list of feature names to enable. + * @return A reference to this Builder for chaining calls. + */ + Builder& features(std::initializer_list list) noexcept; + #if UTILS_HAS_THREADING /** * Creates the filament Engine asynchronously. @@ -477,7 +584,7 @@ class UTILS_PUBLIC Engine { Platform* UTILS_NULLABLE platform = nullptr, void* UTILS_NULLABLE sharedContext = nullptr, const Config* UTILS_NULLABLE config = nullptr) { - return Engine::Builder() + return Builder() .backend(backend) .platform(platform) .sharedContext(sharedContext) @@ -497,7 +604,7 @@ class UTILS_PUBLIC Engine { Platform* UTILS_NULLABLE platform = nullptr, void* UTILS_NULLABLE sharedContext = nullptr, const Config* UTILS_NULLABLE config = nullptr) { - Engine::Builder() + Builder() .backend(backend) .platform(platform) .sharedContext(sharedContext) @@ -522,6 +629,11 @@ class UTILS_PUBLIC Engine { static Engine* UTILS_NULLABLE getEngine(void* UTILS_NONNULL token); #endif + /** + * @return the Driver instance used by this Engine. + * @see OpenGLPlatform + */ + backend::Driver const* UTILS_NONNULL getDriver() const noexcept; /** * Destroy the Engine instance and all associated resources. @@ -643,6 +755,14 @@ class UTILS_PUBLIC Engine { */ bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept; + /** + * Checks if the engine is set up for asynchronous operation. If it returns true, the + * asynchronous versions of the APIs are available for use. + * + * @return true if the engine supports asynchronous operation. + */ + bool isAsynchronousModeEnabled() const noexcept; + /** * Retrieves the configuration settings of this Engine. * @@ -799,9 +919,19 @@ class UTILS_PUBLIC Engine { */ Fence* UTILS_NONNULL createFence() noexcept; + /** + * Creates a Sync. + * @param callback A callback that will be invoked when the handle for + * the created sync is set + * + * @return A pointer to the newly created Sync. + */ + Sync* UTILS_NONNULL createSync() noexcept; + bool destroy(const BufferObject* UTILS_NULLABLE p); //!< Destroys a BufferObject object. bool destroy(const VertexBuffer* UTILS_NULLABLE p); //!< Destroys an VertexBuffer object. bool destroy(const Fence* UTILS_NULLABLE p); //!< Destroys a Fence object. + bool destroy(const Sync* UTILS_NULLABLE p); //!< Destroys a Sync object. bool destroy(const IndexBuffer* UTILS_NULLABLE p); //!< Destroys an IndexBuffer object. bool destroy(const SkinningBuffer* UTILS_NULLABLE p); //!< Destroys a SkinningBuffer object. bool destroy(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Destroys a MorphTargetBuffer object. @@ -835,6 +965,8 @@ class UTILS_PUBLIC Engine { bool isValid(const VertexBuffer* UTILS_NULLABLE p) const; /** Tells whether a Fence object is valid */ bool isValid(const Fence* UTILS_NULLABLE p) const; + /** Tells whether a Sync object is valid */ + bool isValid(const Sync* UTILS_NULLABLE p) const; /** Tells whether an IndexBuffer object is valid */ bool isValid(const IndexBuffer* UTILS_NULLABLE p) const; /** Tells whether a SkinningBuffer object is valid */ @@ -875,6 +1007,70 @@ class UTILS_PUBLIC Engine { /** Tells whether an InstanceBuffer object is valid */ bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const; + /** + * Retrieve the count of each resource tracked by Engine. + * This is intended for debugging. + * @{ + */ + size_t getBufferObjectCount() const noexcept; + size_t getViewCount() const noexcept; + size_t getSceneCount() const noexcept; + size_t getSwapChainCount() const noexcept; + size_t getStreamCount() const noexcept; + size_t getIndexBufferCount() const noexcept; + size_t getSkinningBufferCount() const noexcept; + size_t getMorphTargetBufferCount() const noexcept; + size_t getInstanceBufferCount() const noexcept; + size_t getVertexBufferCount() const noexcept; + size_t getIndirectLightCount() const noexcept; + size_t getMaterialCount() const noexcept; + size_t getTextureCount() const noexcept; + size_t getSkyboxeCount() const noexcept; + size_t getColorGradingCount() const noexcept; + size_t getRenderTargetCount() const noexcept; + /** @} */ + + /** + * This asynchronously executes user-defined commands. The commands are queued sequentially + * alongside other asynchronous operations (see Texture, VertexBuffer, and IndexBuffer) and + * guaranteed to be executed in the exact order they were invoked. + * + * Beware of overusing this method. It shares the execution queue with other asynchronous tasks + * like texture updates, so flooding it can delay those critical engine tasks. The recommended + * practice is to use this method for resource preparation, such as asset loading(images/meshes). + * This facilitates an efficient chaining pattern, where subsequent asynchronous operations + * (e.g., creating textures/vertex buffers) can be initiated directly within the completion + * callback. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param command The custom command to be executed. + * @param handler The handler from which `onComplete` is invoked. If null, it's called from the + * main thread. + * @param onComplete The callback function that runs once the command has finished. + * @param user The custom data that will be passed as an argument to the `onComplete`. + * @return A unique identifier for the asynchronous call. + */ + AsyncCallId runCommandAsync(utils::Invocable&& command, + backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback onComplete, + void* UTILS_NULLABLE user = nullptr); + + /** + * Cancel the pending asynchronous call pointed to by `id`, which is retrieved whenever you + * invoke a non-blocking version of method on an object, such as `Texture::setImageAsync` or + * `BufferObject::setBufferAsync`. + * + * @param id The unique identifier for the asynchronous call to be canceled. + * @return Returns true upon successful cancellation. It returns false if the asynchronous + * operation cannot be canceled because it is currently running, has finished, or has previously + * been canceled. + */ + bool cancelAsyncCall(AsyncCallId id); + /** * 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 @@ -887,6 +1083,25 @@ class UTILS_PUBLIC Engine { */ 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. @@ -903,7 +1118,7 @@ class UTILS_PUBLIC Engine { * * @see setPaused */ - bool isPaused() const noexcept; + bool isPaused() const noexcept(UTILS_HAS_THREADING); /** * Pause or resume rendering thread. @@ -1021,8 +1236,54 @@ class UTILS_PUBLIC Engine { void resetBackendState() noexcept; #endif + /** + * Get the current time. This is a convenience function that simply returns the + * time in nanosecond since epoch of std::chrono::steady_clock. + * A possible implementation is: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * return std::chrono::steady_clock::now().time_since_epoch().count(); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @return current time in nanosecond since epoch of std::chrono::steady_clock. + * @see Renderer::beginFrame() + */ + static uint64_t getSteadyClockTimeNano() noexcept; + + DebugRegistry& getDebugRegistry() noexcept; + /** + * Check if a feature flag exists + * @param name name of the feature flag to check + * @return true if the feature flag exists, false otherwise + */ + inline bool hasFeatureFlag(char const* UTILS_NONNULL name) noexcept { + return getFeatureFlag(name).has_value(); + } + + /** + * Set the value of a non-constant feature flag. + * @param name name of the feature flag to set + * @param value value to set + * @return true if the value was set, false if the feature flag is constant or doesn't exist. + */ + bool setFeatureFlag(char const* UTILS_NONNULL name, bool value) noexcept; + + /** + * Retrieves the value of any feature flag. + * @param name name of the feature flag + * @return the value of the flag if it exists + */ + std::optional getFeatureFlag(char const* UTILS_NONNULL name) const noexcept; + + /** + * Returns a pointer to a non-constant feature flag value. + * @param name name of the feature flag + * @return a pointer to the feature flag value, or nullptr if the feature flag is constant or doesn't exist + */ + bool* UTILS_NULLABLE getFeatureFlagPtr(char const* UTILS_NONNULL name) const noexcept; + protected: //! \privatesection Engine() noexcept = default; diff --git a/package/ios/libs/filament/include/filament/FilamentAPI.h b/package/ios/libs/filament/include/filament/FilamentAPI.h index 19d6ba24..d59e051d 100644 --- a/package/ios/libs/filament/include/filament/FilamentAPI.h +++ b/package/ios/libs/filament/include/filament/FilamentAPI.h @@ -19,6 +19,8 @@ #include #include +#include +#include #include @@ -54,6 +56,37 @@ class UTILS_PUBLIC FilamentAPI { template using BuilderBase = utils::PrivateImplementation; +// This needs to be public because it is used in the following template. +UTILS_PUBLIC void builderMakeName(utils::ImmutableCString& outName, const char* name, size_t len) noexcept; + +template +class UTILS_PUBLIC BuilderNameMixin { +public: + UTILS_DEPRECATED + Builder& name(const char* name, size_t len) noexcept { + builderMakeName(mName, name, len); + return static_cast(*this); + } + + Builder& name(utils::StaticString const& name) noexcept { + builderMakeName(mName, name.data(), name.size()); + return static_cast(*this); + } + + utils::ImmutableCString const& getName() const noexcept { return mName; } + + utils::ImmutableCString const& getNameOrDefault() const noexcept { + if (const auto& name = getName(); !name.empty()) { + return name; + } + static const utils::ImmutableCString sDefaultName = "(none)"; + return sDefaultName; + } + +private: + utils::ImmutableCString mName; +}; + } // namespace filament #endif // TNT_FILAMENT_FILAMENTAPI_H diff --git a/package/ios/libs/filament/include/filament/IndexBuffer.h b/package/ios/libs/filament/include/filament/IndexBuffer.h index 35b8a10e..9ac7dd3f 100644 --- a/package/ios/libs/filament/include/filament/IndexBuffer.h +++ b/package/ios/libs/filament/include/filament/IndexBuffer.h @@ -26,7 +26,9 @@ #include #include +#include +#include #include #include @@ -50,6 +52,9 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { public: using BufferDescriptor = backend::BufferDescriptor; + using AsyncCompletionCallback = + std::function; + using AsyncCallId = backend::AsyncCallId; /** * Type of the index buffer @@ -59,7 +64,7 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { UINT = uint8_t(backend::ElementType::UINT), //!< 32-bit indices }; - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -83,6 +88,56 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { */ Builder& bufferType(IndexType indexType) noexcept; + /** + * Associate an optional name with this IndexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this IndexBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this IndexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this IndexBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + + /** + * Specifies a callback that will execute once the resource's data has been fully allocated + * within the GPU memory. This enables the resource creation process to be handled + * asynchronously. + * + * Any asynchronous calls made during a resource's asynchronous creation (using this method) + * are safe because they are queued and executed in sequence. However, invoking regular + * methods on the same resource before it's fully ready is unsafe and may cause undefined + * behavior. Users can call the `isCreationComplete()` method for the resource to confirm + * when the resource is ready for regular API calls. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * @return This Builder, for chaining calls. + */ + Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback = nullptr, + void* UTILS_NULLABLE user = nullptr) noexcept; + /** * Creates the IndexBuffer object and returns a pointer to it. After creation, the index * buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer. @@ -103,22 +158,59 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI { }; /** - * Asynchronously copy-initializes a region of this IndexBuffer from the data provided. + * Copy-initializes a region of this IndexBuffer from the data provided. * * @param engine Reference to the filament::Engine to associate this IndexBuffer with. * @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer. * BufferDescriptor points to raw, untyped data that will be interpreted as * either 16-bit or 32-bits indices based on the Type of this IndexBuffer. - * @param byteOffset Offset in *bytes* into the IndexBuffer + * @param byteOffset Offset in *bytes* into the IndexBuffer. Must be multiple of 4. */ void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + /** + * An asynchronous version of `setBuffer()`. + * Copy-initializes a region of this IndexBuffer from the data provided. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param engine Reference to the filament::Engine to associate this IndexBuffer with. + * @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer. + * BufferDescriptor points to raw, untyped data that will be interpreted as + * either 16-bit or 32-bits indices based on the Type of this IndexBuffer. + * @param byteOffset Offset in *bytes* into the IndexBuffer. Must be multiple of 4. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + */ + AsyncCallId setBufferAsync(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset, + backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr); + /** * Returns the size of this IndexBuffer in elements. * @return The number of indices the IndexBuffer holds. */ size_t getIndexCount() const noexcept; + /** + * This non-blocking method checks if the resource has finished creation. If the resource + * creation was initiated asynchronously, it will return true only after all related + * asynchronous tasks are complete. If the resource was created normally without using async + * method, it will always return true. + * + * @return Whether the resource is created. + * + * @see Builder::async() + */ + bool isCreationComplete() const noexcept; + protected: // prevent heap allocation ~IndexBuffer() = default; diff --git a/package/ios/libs/filament/include/filament/IndirectLight.h b/package/ios/libs/filament/include/filament/IndirectLight.h index c230dac8..f5c2cfbe 100644 --- a/package/ios/libs/filament/include/filament/IndirectLight.h +++ b/package/ios/libs/filament/include/filament/IndirectLight.h @@ -158,6 +158,8 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { * * @return This Builder, for chaining calls. * + * @see Material::Builder::sphericalHarmonicsBandCount() + * * @note * Because the coefficients are pre-scaled, `sh[0]` is the environment's * average irradiance. @@ -337,13 +339,24 @@ class UTILS_PUBLIC IndirectLight : public FilamentAPI { static math::float4 getColorEstimate(const math::float3 sh[UTILS_NONNULL 9], math::float3 direction) noexcept; - - /** @deprecated use static versions instead */ - UTILS_DEPRECATED + /** + * Helper to estimate the direction of the dominant light in the environment represented by + * spherical harmonics. + * Spherical harmonics must be set in the Builder or the result is undefined. + * @see getDirectionEstimate(const math::float3) + * @see Builder::irradiance(uint8_t, math::float3 const*) + * @see Builder::radiance(uint8_t, math::float3 const*) + */ math::float3 getDirectionEstimate() const noexcept; - /** @deprecated use static versions instead */ - UTILS_DEPRECATED + /** + * Helper to estimate the color and relative intensity of the environment represented by + * spherical harmonics in a given direction. + * Spherical harmonics must be set in the Builder or the result is undefined. + * @see getColorEstimate(const math::float3, math::float3) + * @see Builder::irradiance(uint8_t, math::float3 const*) + * @see Builder::radiance(uint8_t, math::float3 const*) + */ math::float4 getColorEstimate(math::float3 direction) const noexcept; protected: diff --git a/package/ios/libs/filament/include/filament/InstanceBuffer.h b/package/ios/libs/filament/include/filament/InstanceBuffer.h index 2135152d..8e7f788c 100644 --- a/package/ios/libs/filament/include/filament/InstanceBuffer.h +++ b/package/ios/libs/filament/include/filament/InstanceBuffer.h @@ -21,6 +21,7 @@ #include #include +#include #include @@ -38,13 +39,13 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { struct BuilderDetails; public: - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: /** - * @param instanceCount the number of instances this InstanceBuffer will support, must be + * @param instanceCount The number of instances this InstanceBuffer will support, must be * >= 1 and <= \c Engine::getMaxAutomaticInstances() * @see Engine::getMaxAutomaticInstances */ @@ -70,10 +71,37 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { */ Builder& localTransforms(math::mat4f const* UTILS_NULLABLE localTransforms) noexcept; + /** + * Associate an optional name with this InstanceBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this InstanceBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this InstanceBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this InstanceBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the InstanceBuffer object and returns a pointer to it. */ - InstanceBuffer* UTILS_NONNULL build(Engine& engine); + InstanceBuffer* UTILS_NONNULL build(Engine& engine) const; private: friend class FInstanceBuffer; @@ -96,6 +124,13 @@ class UTILS_PUBLIC InstanceBuffer : public FilamentAPI { void setLocalTransforms(math::mat4f const* UTILS_NONNULL localTransforms, size_t count, size_t offset = 0); + /** + * Returns the local transform for a given instance. + * @param index The index of the instance. + * @return The local transform of the instance. + */ + math::mat4f const& getLocalTransform(size_t index); + protected: // prevent heap allocation ~InstanceBuffer() = default; diff --git a/package/ios/libs/filament/include/filament/Material.h b/package/ios/libs/filament/include/filament/Material.h index b4b9bbed..3625bf6e 100644 --- a/package/ios/libs/filament/include/filament/Material.h +++ b/package/ios/libs/filament/include/filament/Material.h @@ -69,6 +69,20 @@ class UTILS_PUBLIC Material : public FilamentAPI { using ShaderModel = backend::ShaderModel; using SubpassType = backend::SubpassType; + /** + * Defines whether a material instance should use UBO batching or not. + */ + enum class UboBatchingMode { + /** + * For default, it follows the engine settings. + * If UBO batching is enabled on the engine and the material domain is SURFACE, it + * turns on the UBO batching. Otherwise, it turns off the UBO batching. + */ + DEFAULT, + //! Disable the Ubo Batching for this material + DISABLED, + }; + /** * Holds information about a material parameter. */ @@ -103,6 +117,11 @@ class UTILS_PUBLIC Material : public FilamentAPI { Builder& operator=(Builder const& rhs) noexcept; Builder& operator=(Builder&& rhs) noexcept; + enum class ShadowSamplingQuality : uint8_t { + HARD, // 2x2 PCF + LOW // 3x3 gaussian filter + }; + /** * Specifies the material data. The material data is a binary blob produced by * libfilamat or by matc. @@ -113,10 +132,10 @@ class UTILS_PUBLIC Material : public FilamentAPI { Builder& package(const void* UTILS_NONNULL payload, size_t size); template - using is_supported_constant_parameter_t = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value>::type; + using is_supported_constant_parameter_t = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v>; /** * Specialize a constant parameter specified in the material definition with a concrete @@ -140,6 +159,33 @@ class UTILS_PUBLIC Material : public FilamentAPI { return constant(name, strlen(name), value); } + /** + * Sets the quality of the indirect lights computations. This is only taken into account + * if this material is lit and in the surface domain. This setting will affect the + * IndirectLight computation if one is specified on the Scene and Spherical Harmonics + * are used for the irradiance. + * + * @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default). + * @return Reference to this Builder for chaining calls. + * @see IndirectLight + */ + Builder& sphericalHarmonicsBandCount(size_t shBandCount) noexcept; + + /** + * Set the quality of shadow sampling. This is only taken into account + * if this material is lit and in the surface domain. + * @param quality + * @return + */ + Builder& shadowSamplingQuality(ShadowSamplingQuality quality) noexcept; + + /** + * Set the batching mode of the instances created from this material. + * @param uboBatchingMode + * @return + */ + Builder& uboBatching(UboBatchingMode uboBatchingMode) noexcept; + /** * Creates the Material object and returns a pointer to it. * @@ -152,7 +198,7 @@ class UTILS_PUBLIC Material : public FilamentAPI { * memory or other resources. * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. */ - Material* UTILS_NULLABLE build(Engine& engine); + Material* UTILS_NULLABLE build(Engine& engine) const; private: friend class FMaterial; }; @@ -325,6 +371,24 @@ class UTILS_PUBLIC Material : public FilamentAPI { //! Indicates whether an existing parameter is a sampler or not. bool isSampler(const char* UTILS_NONNULL name) const noexcept; + /** + * Returns a view of the material source (.mat which is a JSON-ish file) string, + * if it has been set. Otherwise, it returns a view of an empty string. + * The lifetime of the string_view is tied to the lifetime of the Material. + */ + std::string_view getSource() const noexcept; + /** + * + * Gets the name of the transform field associated for the given sampler parameter. + * In the case where the parameter does not have a transform name field, it will return nullptr. + * + * @param samplerName the name of the sampler parameter to query. + * + * @return If exists, the transform name value otherwise returns a nullptr. + */ + const char* UTILS_NULLABLE getParameterTransformName( + const char* UTILS_NONNULL samplerName) const noexcept; + /** * Sets the value of the given parameter on this material's default instance. * diff --git a/package/ios/libs/filament/include/filament/MaterialChunkType.h b/package/ios/libs/filament/include/filament/MaterialChunkType.h index 4a4561c1..e321b386 100644 --- a/package/ios/libs/filament/include/filament/MaterialChunkType.h +++ b/package/ios/libs/filament/include/filament/MaterialChunkType.h @@ -45,18 +45,20 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialEssl1 = charTo64bitNum("MAT_ESS1"), MaterialSpirv = charTo64bitNum("MAT_SPIR"), MaterialMetal = charTo64bitNum("MAT_METL"), + MaterialWgsl = charTo64bitNum("MAT_WGSL"), MaterialMetalLibrary = charTo64bitNum("MAT_MLIB"), MaterialShaderModels = charTo64bitNum("MAT_SMDL"), - MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"), - MaterialUniformBindings = charTo64bitNum("MAT_UNIF"), MaterialBindingUniformInfo = charTo64bitNum("MAT_UFRM"), MaterialAttributeInfo = charTo64bitNum("MAT_ATTR"), + MaterialDescriptorBindingsInfo = charTo64bitNum("MAT_DBDI"), + MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"), MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialConstants = charTo64bitNum("MAT_CONS"), MaterialPushConstants = charTo64bitNum("MAT_PCON"), MaterialName = charTo64bitNum("MAT_NAME"), MaterialVersion = charTo64bitNum("MAT_VERS"), + MaterialCompilationParameters = charTo64bitNum("MAT_CPRM"), MaterialCacheId = charTo64bitNum("MAT_UUID"), MaterialFeatureLevel = charTo64bitNum("MAT_FEAT"), MaterialShading = charTo64bitNum("MAT_SHAD"), @@ -92,10 +94,15 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialVertexDomain = charTo64bitNum("MAT_VEDO"), MaterialInterpolation = charTo64bitNum("MAT_INTR"), + MaterialStereoscopicType = charTo64bitNum("MAT_STER"), DictionaryText = charTo64bitNum("DIC_TEXT"), DictionarySpirv = charTo64bitNum("DIC_SPIR"), DictionaryMetalLibrary = charTo64bitNum("DIC_MLIB"), + + MaterialCrc32 = charTo64bitNum("MAT_CRC "), + + MaterialSource = charTo64bitNum("MAT_SRC "), }; } // namespace filamat diff --git a/package/ios/libs/filament/include/filament/MaterialEnums.h b/package/ios/libs/filament/include/filament/MaterialEnums.h index c15c6c45..fb2bea79 100644 --- a/package/ios/libs/filament/include/filament/MaterialEnums.h +++ b/package/ios/libs/filament/include/filament/MaterialEnums.h @@ -28,7 +28,16 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 53; +static constexpr size_t MATERIAL_VERSION = 68; + +// Those are the api levels that are used in the source material file (.mat) +// +// The level used for the apis that are already public and stable. Any released api supports +// backward compatibility (i.e. No breaking changes will be introduced for those apis.) +static constexpr uint32_t RELEASED_MATERIAL_API_LEVEL = 1; +// The level used for the apis that are currently under development or development has completed +// but may introduce breaking changes. +static constexpr uint32_t UNSTABLE_MATERIAL_API_LEVEL = 2; /** * Supported shading models @@ -203,7 +212,7 @@ enum class ReflectionMode : uint8_t { // can't really use std::underlying_type::type because the driver takes a uint32_t using AttributeBitset = utils::bitset32; -static constexpr size_t MATERIAL_PROPERTIES_COUNT = 27; +static constexpr size_t MATERIAL_PROPERTIES_COUNT = 31; enum class Property : uint8_t { BASE_COLOR, //!< float4, all shading models ROUGHNESS, //!< float, lit shading models only @@ -230,8 +239,12 @@ enum class Property : uint8_t { ABSORPTION, //!< float3, how much light is absorbed by the material TRANSMISSION, //!< float, how much light is refracted through the material IOR, //!< float, material's index of refraction + DISPERSION, //!< float, material's dispersion MICRO_THICKNESS, //!< float, thickness of the thin layer BENT_NORMAL, //!< float3, all shading models only, except unlit + SPECULAR_FACTOR, //!< float, lit shading models only, except subsurface and cloth + SPECULAR_COLOR_FACTOR, //!< float3, lit shading models only, except subsurface and cloth + SHADOW_STRENGTH, //!< float, [0, 1] strength of shadows received by this material // when adding new Properties, make sure to update MATERIAL_PROPERTIES_COUNT }; @@ -255,4 +268,7 @@ enum class UserVariantFilterBit : UserVariantFilterMask { template<> struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableIntegerOperators + : public std::true_type {}; + #endif diff --git a/package/ios/libs/filament/include/filament/MaterialInstance.h b/package/ios/libs/filament/include/filament/MaterialInstance.h index 2b8aaa9a..b052c8f7 100644 --- a/package/ios/libs/filament/include/filament/MaterialInstance.h +++ b/package/ios/libs/filament/include/filament/MaterialInstance.h @@ -19,7 +19,7 @@ #include #include - +#include #include #include @@ -56,35 +56,36 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { }; public: - using CullingMode = filament::backend::CullingMode; + using CullingMode = backend::CullingMode; + // ReSharper disable once CppRedundantQualifier using TransparencyMode = filament::TransparencyMode; - using DepthFunc = filament::backend::SamplerCompareFunc; - using StencilCompareFunc = filament::backend::SamplerCompareFunc; - using StencilOperation = filament::backend::StencilOperation; - using StencilFace = filament::backend::StencilFace; + using DepthFunc = backend::SamplerCompareFunc; + using StencilCompareFunc = backend::SamplerCompareFunc; + using StencilOperation = backend::StencilOperation; + using StencilFace = backend::StencilFace; template - using is_supported_parameter_t = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || + using is_supported_parameter_t = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || // these types are slower as they need a layout conversion - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value - >::type; + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v + >; /** * Creates a new MaterialInstance using another MaterialInstance as a template for initialization. @@ -121,13 +122,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated string literal */ template> - inline void setParameter(StringLiteral name, T const& value) { + void setParameter(StringLiteral const name, T const& value) { setParameter(name.data, name.size, value); } /** inline helper to provide the name as a null-terminated C string */ template> - inline void setParameter(const char* UTILS_NONNULL name, T const& value) { + void setParameter(const char* UTILS_NONNULL name, T const& value) { setParameter(name, strlen(name), value); } @@ -148,14 +149,14 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated string literal */ template> - inline void setParameter(StringLiteral name, const T* UTILS_NONNULL values, size_t count) { + void setParameter(StringLiteral const name, const T* UTILS_NONNULL values, size_t const count) { setParameter(name.data, name.size, values, count); } /** inline helper to provide the name as a null-terminated C string */ template> - inline void setParameter(const char* UTILS_NONNULL name, - const T* UTILS_NONNULL values, size_t count) { + void setParameter(const char* UTILS_NONNULL name, + const T* UTILS_NONNULL values, size_t const count) { setParameter(name, strlen(name), values, count); } @@ -176,14 +177,14 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler); /** inline helper to provide the name as a null-terminated string literal */ - inline void setParameter(StringLiteral name, - Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { + void setParameter(StringLiteral const name, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { setParameter(name.data, name.size, texture, sampler); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* UTILS_NONNULL name, - Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { + void setParameter(const char* UTILS_NONNULL name, + Texture const* UTILS_NULLABLE texture, TextureSampler const& sampler) { setParameter(name, strlen(name), texture, sampler); } @@ -202,12 +203,12 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { RgbType type, math::float3 color); /** inline helper to provide the name as a null-terminated string literal */ - inline void setParameter(StringLiteral name, RgbType type, math::float3 color) { + void setParameter(StringLiteral const name, RgbType const type, math::float3 const color) { setParameter(name.data, name.size, type, color); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* UTILS_NONNULL name, RgbType type, math::float3 color) { + void setParameter(const char* UTILS_NONNULL name, RgbType const type, math::float3 const color) { setParameter(name, strlen(name), type, color); } @@ -226,12 +227,12 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { RgbaType type, math::float4 color); /** inline helper to provide the name as a null-terminated string literal */ - inline void setParameter(StringLiteral name, RgbaType type, math::float4 color) { + void setParameter(StringLiteral const name, RgbaType const type, math::float4 const color) { setParameter(name.data, name.size, type, color); } /** inline helper to provide the name as a null-terminated C string */ - inline void setParameter(const char* UTILS_NONNULL name, RgbaType type, math::float4 color) { + void setParameter(const char* UTILS_NONNULL name, RgbaType const type, math::float4 const color) { setParameter(name, strlen(name), type, color); } @@ -251,13 +252,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { /** inline helper to provide the name as a null-terminated C string */ template> - inline T getParameter(StringLiteral name) const { + T getParameter(StringLiteral const name) const { return getParameter(name.data, name.size); } /** inline helper to provide the name as a null-terminated C string */ template> - inline T getParameter(const char* UTILS_NONNULL name) const { + T getParameter(const char* UTILS_NONNULL name) const { return getParameter(name, strlen(name)); } @@ -376,11 +377,22 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { */ void setCullingMode(CullingMode culling) noexcept; + /** + * Overrides the default triangle culling state that was set on the material separately for the + * color and shadow passes + */ + void setCullingMode(CullingMode colorPassCullingMode, CullingMode shadowPassCullingMode) noexcept; + /** * Returns the face culling mode. */ CullingMode getCullingMode() const noexcept; + /** + * Returns the face culling mode for the shadow passes. + */ + CullingMode getShadowCullingMode() const noexcept; + /** * Overrides the default color-buffer write state that was set on the material. */ @@ -518,6 +530,13 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI { void setStencilWriteMask(uint8_t writeMask, StencilFace face = StencilFace::FRONT_AND_BACK) noexcept; + /** + * PostProcess and compute domain material instance must be commited manually. This call has + * no effect on surface domain materials. + * @param engine Filament engine + */ + void commit(Engine& engine) const; + protected: // prevent heap allocation ~MaterialInstance() = default; diff --git a/package/ios/libs/filament/include/filament/MorphTargetBuffer.h b/package/ios/libs/filament/include/filament/MorphTargetBuffer.h index 655bb8d8..594f1a6c 100644 --- a/package/ios/libs/filament/include/filament/MorphTargetBuffer.h +++ b/package/ios/libs/filament/include/filament/MorphTargetBuffer.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -39,7 +40,7 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { struct BuilderDetails; public: - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -63,6 +64,33 @@ class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { */ Builder& count(size_t count) noexcept; + /** + * Associate an optional name with this MorphTargetBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this MorphTargetBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this MorphTargetBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this MorphTargetBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the MorphTargetBuffer object and returns a pointer to it. * diff --git a/package/ios/libs/filament/include/filament/Options.h b/package/ios/libs/filament/include/filament/Options.h index 3966053e..422a0222 100644 --- a/package/ios/libs/filament/include/filament/Options.h +++ b/package/ios/libs/filament/include/filament/Options.h @@ -70,8 +70,8 @@ enum class BlendMode : uint8_t { * * \note * Dynamic resolution is only supported on platforms where the time to render - * a frame can be measured accurately. Dynamic resolution is currently only - * supported on Android. + * a frame can be measured accurately. On platforms where this is not supported, + * Dynamic Resolution can't be enabled unless minScale == maxScale. * * @see Renderer::FrameRateOptions * @@ -86,12 +86,15 @@ 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. + * + * caveat: currently, 'quality' is always set to LOW if the View is TRANSLUCENT. */ QualityLevel quality = QualityLevel::LOW; }; @@ -164,7 +167,9 @@ struct BloomOptions { }; /** - * Options to control large-scale fog in the scene + * Options to control large-scale fog in the scene. Materials can enable the `linearFog` property, + * which uses a simplified, linear equation for fog calculation; in this mode, the heightFalloff + * is ignored as well as the mipmap selection in IBL or skyColor mode. */ struct FogOptions { /** @@ -183,7 +188,7 @@ struct FogOptions { float cutOffDistance = INFINITY; /** - * fog's maximum opacity between 0 and 1 + * fog's maximum opacity between 0 and 1. Ignored in `linearFog` mode. */ float maximumOpacity = 1.0f; @@ -193,12 +198,15 @@ struct FogOptions { float height = 0.0f; /** - * How fast the fog dissipates with altitude. heightFalloff has a unit of [1/m]. + * How fast the fog dissipates with the altitude. heightFalloff has a unit of [1/m]. * It can be expressed as 1/H, where H is the altitude change in world units [m] that causes a * factor 2.78 (e) change in fog density. * * A falloff of 0 means the fog density is constant everywhere and may result is slightly * faster computations. + * + * In `linearFog` mode, only use to compute the slope of the linear equation. Completely + * ignored if set to 0. */ float heightFalloff = 1.0f; @@ -220,7 +228,7 @@ struct FogOptions { LinearColor color = { 1.0f, 1.0f, 1.0f }; /** - * Extinction factor in [1/m] at altitude 'height'. The extinction factor controls how much + * Extinction factor in [1/m] at an altitude 'height'. The extinction factor controls how much * light is absorbed and out-scattered per unit of distance. Each unit of extinction reduces * the incoming light to 37% of its original value. * @@ -229,11 +237,16 @@ struct FogOptions { * the composition of the fog/atmosphere. * * For historical reason this parameter is called `density`. + * + * In `linearFog` mode this is the slope of the linear equation if heightFalloff is set to 0. + * Otherwise, heightFalloff affects the slope calculation such that it matches the slope of + * the standard equation at the camera height. */ float density = 0.1f; /** * Distance in world units [m] from the camera where the Sun in-scattering starts. + * Ignored in `linearFog` mode. */ float inScatteringStart = 0.0f; @@ -242,6 +255,7 @@ struct FogOptions { * is scattered (by the fog) towards the camera. * Size of the Sun in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100). * Smaller values result is a larger scattering size. + * Ignored in `linearFog` mode. */ float inScatteringSize = -1.0f; @@ -269,6 +283,8 @@ struct FogOptions { * * `fogColorFromIbl` is ignored when skyTexture is specified. * + * In `linearFog` mode mipmap level 0 is always used. + * * @see Texture * @see fogColorFromIbl */ @@ -283,7 +299,7 @@ struct FogOptions { /** * Options to control Depth of Field (DoF) effect in the scene. * - * cocScale can be used to set the depth of field blur independently from the camera + * cocScale can be used to set the depth of field blur independently of the camera * aperture, e.g. for artistic reasons. This can be achieved by setting: * cocScale = cameraAperture / desiredDoFAperture * @@ -373,18 +389,30 @@ struct RenderQuality { * @see setAmbientOcclusionOptions() */ struct AmbientOcclusionOptions { + enum class AmbientOcclusionType : uint8_t { + SAO, //!< use Scalable Ambient Occlusion + GTAO, //!< use Ground Truth-Based Ambient Occlusion + }; + + AmbientOcclusionType aoType = AmbientOcclusionType::SAO;//!< Type of ambient occlusion algorithm. float radius = 0.3f; //!< Ambient Occlusion radius in meters, between 0 and ~10. float power = 1.0f; //!< Controls ambient occlusion's contrast. Must be positive. - float bias = 0.0005f; //!< Self-occlusion bias in meters. Use to avoid self-occlusion. Between 0 and a few mm. + + /** + * Self-occlusion bias in meters. Use to avoid self-occlusion. + * Between 0 and a few mm. No effect when aoType set to GTAO + */ + float bias = 0.0005f; + float resolution = 0.5f;//!< How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0. float intensity = 1.0f; //!< Strength of the Ambient Occlusion effect. float bilateralThreshold = 0.05f; //!< depth distance that constitute an edge for filtering - QualityLevel quality = QualityLevel::LOW; //!< affects # of samples used for AO. - QualityLevel lowPassFilter = QualityLevel::MEDIUM; //!< affects AO smoothness + QualityLevel quality = QualityLevel::LOW; //!< affects # of samples used for AO and params for filtering + QualityLevel lowPassFilter = QualityLevel::MEDIUM; //!< affects AO smoothness. Recommend setting to HIGH when aoType set to GTAO. QualityLevel upsampling = QualityLevel::LOW; //!< affects AO buffer upsampling quality bool enabled = false; //!< enables or disables screen-space ambient occlusion bool bentNormals = false; //!< enables bent normals computation from AO, and specular AO - float minHorizonAngleRad = 0.0f; //!< min angle in radian to consider + float minHorizonAngleRad = 0.0f; //!< min angle in radian to consider. No effect when aoType set to GTAO. /** * Screen Space Cone Tracing (SSCT) options * Ambient shadows from dominant light @@ -402,6 +430,29 @@ struct AmbientOcclusionOptions { bool enabled = false; //!< enables or disables SSCT }; Ssct ssct; // %codegen_skip_javascript% %codegen_java_flatten% + + /** + * Ground Truth-base Ambient Occlusion (GTAO) options + */ + struct Gtao { + uint8_t sampleSliceCount = 4; //!< # of slices. Higher value makes less noise. + uint8_t sampleStepsPerSlice = 3; //!< # of steps the radius is divided into for integration. Higher value makes less bias. + float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0. No effect when useVisibilityBitmasks sets to true. + + /** + * Enables or disables visibility bitmasks mode. Notes that bent normal doesn't work under this mode. + * Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation. + */ + bool useVisibilityBitmasks = false; + float constThickness = 0.5f; //!< constant thickness value of objects on the screen in world space. Only take effect when useVisibilityBitmasks is set to true. + + /** + * Increase thickness with distance to maintain detail on distant surfaces. + * Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation. + */ + bool linearThickness = false; + }; + Gtao gtao; // %codegen_skip_javascript% %codegen_java_flatten% }; /** @@ -438,16 +489,15 @@ struct MultiSampleAntiAliasingOptions { * @see setTemporalAntiAliasingOptions() */ struct TemporalAntiAliasingOptions { - float filterWidth = 1.0f; //!< reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother) + float filterWidth = 1.0f; //!< @deprecated has no effect. float feedback = 0.12f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). float lodBias = -1.0f; //!< texturing lod bias (typically -1 or -2) float sharpness = 0.0f; //!< post-TAA sharpen, especially useful when upscaling is true. bool enabled = false; //!< enables or disables temporal anti-aliasing - bool upscaling = false; //!< 4x TAA upscaling. Disables Dynamic Resolution. [BETA] + float upscaling = 1.0f; //!< Upscaling factor. Disables Dynamic Resolution. [BETA] enum class BoxType : uint8_t { AABB, //!< use an AABB neighborhood - VARIANCE, //!< use the variance of the neighborhood (not recommended) AABB_VARIANCE //!< use both AABB and variance }; @@ -468,6 +518,7 @@ struct TemporalAntiAliasingOptions { bool filterHistory = true; //!< whether to filter the history buffer bool filterInput = true; //!< whether to apply the reconstruction filter to the input bool useYCoCg = false; //!< whether to use the YcoCg color-space for history rejection + bool hdr = true; //!< set to true for HDR content BoxType boxType = BoxType::AABB; //!< type of color gamut box BoxClipping boxClipping = BoxClipping::ACCURATE; //!< clipping algorithm JitterPattern jitterPattern = JitterPattern::HALTON_23_X16; //! Jitter Pattern diff --git a/package/ios/libs/filament/include/filament/RenderTarget.h b/package/ios/libs/filament/include/filament/RenderTarget.h index fc76111d..02eb6714 100644 --- a/package/ios/libs/filament/include/filament/RenderTarget.h +++ b/package/ios/libs/filament/include/filament/RenderTarget.h @@ -81,7 +81,7 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { }; //! Use Builder to construct a RenderTarget object instance - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -118,7 +118,7 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { Builder& mipLevel(AttachmentPoint attachment, uint8_t level) noexcept; /** - * Sets the cubemap face for a given attachment point. + * Sets the face for cubemap textures at the given attachment point. * * @param attachment The attachment point. * @param face The associated cubemap face. @@ -127,7 +127,12 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { Builder& face(AttachmentPoint attachment, CubemapFace face) noexcept; /** - * Sets the layer for a given attachment point (for 3D textures). + * Sets an index of a single layer for 2d array, cubemap array, and 3d textures at the given + * attachment point. + * + * For cubemap array textures, layer is translated into an array index and face according to + * - index: layer / 6 + * - face: layer % 6 * * @param attachment The attachment point. * @param layer The associated cubemap layer. @@ -135,6 +140,27 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { */ Builder& layer(AttachmentPoint attachment, uint32_t layer) noexcept; + /** + * Sets the starting index of the 2d array textures for multiview at the given attachment + * point. + * + * This requires COLOR and DEPTH attachments (if set) to be of 2D array textures. + * + * @param attachment The attachment point. + * @param layerCount The number of layers used for multiview, starting from baseLayer. + * @param baseLayer The starting index of the 2d array texture. + * @return A reference to this Builder for chaining calls. + */ + Builder& multiview(AttachmentPoint attachment, uint8_t layerCount, uint8_t baseLayer = 0) noexcept; + + /** + * Sets the number of samples used for MSAA (Multisample Anti-Aliasing). + * + * @param samples The number of samples used for multisampling. + * @return A reference to this Builder for chaining calls. + */ + Builder& samples(uint8_t samples) noexcept; + /** * Creates the RenderTarget object and returns a pointer to it. * diff --git a/package/ios/libs/filament/include/filament/RenderableManager.h b/package/ios/libs/filament/include/filament/RenderableManager.h index 363ef2d7..1428ed1e 100644 --- a/package/ios/libs/filament/include/filament/RenderableManager.h +++ b/package/ios/libs/filament/include/filament/RenderableManager.h @@ -220,7 +220,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * the renderable are immutable. * STATIC geometry has the same restrictions as STATIC_BOUNDS, but in addition disallows * skinning, morphing and changing the VertexBuffer or IndexBuffer in any way. - * @param enable whether this renderable has static bounds. false by default. + * @param type type of geometry. */ Builder& geometryType(GeometryType type) noexcept; @@ -297,7 +297,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { Builder& priority(uint8_t priority) noexcept; /** - * Set the channel this renderable is associated to. There can be 4 channels. + * Set the channel this renderable is associated to. There can be 8 channels. * All renderables in a given channel are rendered together, regardless of anything else. * They are sorted as usual within a channel. * Channels work similarly to priorities, except that they enforce the strongest ordering. @@ -305,7 +305,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * Channels 0 and 1 may not have render primitives using a material with `refractionType` * set to `screenspace`. * - * @param channel clamped to the range [0..3], defaults to 2. + * @param channel clamped to the range [0..7], defaults to 2. * * @return Builder reference for chaining calls. * @@ -454,7 +454,7 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * * @param primitiveIndex zero-based index of the primitive, must be less than the primitive * count passed to Builder constructor - * @param indicesAndWeightsVectors pairs of bone index and bone weight for all vertices of + * @param indicesAndWeightsVector pairs of bone index and bone weight for all vertices of * the primitive sequentially * * @return Builder reference for chaining calls. @@ -489,43 +489,20 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { * For standard morphing, A MorphTargetBuffer must be provided. * Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets. * - * For legacy morphing, the attached VertexBuffer must provide data in the - * appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only - * supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must - * be enabled on the material definition: either via the legacyMorphing material attribute - * or by calling filamat::MaterialBuilder::useLegacyMorphing(). - * * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis * to advance the animation. */ Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept; - /** - * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead - */ - Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, - size_t offset, size_t count) noexcept; - - /** - * @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead - */ - inline Builder& morphing(uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { - return morphing(level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); - } - /** * Specifies the the range of the MorphTargetBuffer to use with this primitive. * * @param level the level of detail (lod), only 0 can be specified * @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor * @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices) - * @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3) */ - Builder& morphing(uint8_t level, size_t primitiveIndex, - size_t offset, size_t count) noexcept; + Builder& morphing(uint8_t level, + size_t primitiveIndex, size_t offset) noexcept; /** @@ -620,21 +597,6 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { friend class FEngine; friend class FRenderPrimitive; friend class FRenderableManager; - struct Entry { - VertexBuffer* UTILS_NULLABLE vertices = nullptr; - IndexBuffer* UTILS_NULLABLE indices = nullptr; - size_t offset = 0; - size_t count = 0; - MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr; - PrimitiveType type = PrimitiveType::TRIANGLES; - uint16_t blendOrder = 0; - bool globalBlendOrderEnabled = false; - struct { - MorphTargetBuffer* UTILS_NULLABLE buffer = nullptr; - size_t offset = 0; - size_t count = 0; - } morphing; - }; }; /** @@ -786,25 +748,13 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /** * Associates a MorphTargetBuffer to the given primitive. */ - void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - size_t offset, size_t count); - - /** @deprecated */ - void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count); - - /** @deprecated */ - inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, - MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) { - setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0, - morphTargetBuffer->getVertexCount()); - } + void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex, + size_t offset); /** - * Get a MorphTargetBuffer to the given primitive or null if it doesn't exist. + * Get a MorphTargetBuffer to the given renderable or null if it doesn't exist. */ - MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBufferAt(Instance instance, - uint8_t level, size_t primitiveIndex) const noexcept; + MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBuffer(Instance instance) const noexcept; /** * Gets the number of morphing in the given entity. @@ -833,6 +783,13 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { */ size_t getPrimitiveCount(Instance instance) const noexcept; + /** + * Returns the number of instances for this renderable. + * @param instance Instance of the component obtained from getInstance(). + * @return The number of instances. + */ + size_t getInstanceCount(Instance instance) const noexcept; + /** * Changes the material instance binding for the given primitive. * @@ -848,6 +805,13 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { void setMaterialInstanceAt(Instance instance, size_t primitiveIndex, MaterialInstance const* UTILS_NONNULL materialInstance); + /** + * Clear the MaterialInstance for the given primitive. + * @param instance Renderable's instance + * @param primitiveIndex Primitive index + */ + void clearMaterialInstanceAt(Instance instance, size_t primitiveIndex); + /** * Retrieves the material instance that is bound to the given primitive. */ @@ -895,20 +859,20 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { /*! \cond PRIVATE */ template struct is_supported_vector_type { - using type = typename std::enable_if< - std::is_same::value || - std::is_same::value || - std::is_same::value || - std::is_same::value - >::type; + using type = std::enable_if_t< + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v + >; }; template struct is_supported_index_type { - using type = typename std::enable_if< - std::is_same::value || - std::is_same::value - >::type; + using type = std::enable_if_t< + std::is_same_v || + std::is_same_v + >; }; /*! \endcond */ diff --git a/package/ios/libs/filament/include/filament/Renderer.h b/package/ios/libs/filament/include/filament/Renderer.h index fdc291b1..53c13cab 100644 --- a/package/ios/libs/filament/include/filament/Renderer.h +++ b/package/ios/libs/filament/include/filament/Renderer.h @@ -22,9 +22,11 @@ #include #include +#include #include +#include #include namespace filament { @@ -81,6 +83,50 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { UTILS_DEPRECATED uint64_t vsyncOffsetNanos = 0; }; + /** + * Timing information about a frame + * @see getFrameInfoHistory() + */ + struct FrameInfo { + /** duration in nanosecond since epoch of std::steady_clock */ + using time_point_ns = int64_t; + /** duration in nanosecond on the std::steady_clock */ + using duration_ns = int64_t; + static constexpr time_point_ns INVALID = -1; //!< value not supported + static constexpr time_point_ns PENDING = -2; //!< value not yet available + uint32_t frameId; //!< monotonically increasing frame identifier + duration_ns gpuFrameDuration; //!< frame duration on the GPU in nanosecond [ns] + duration_ns denoisedGpuFrameDuration; //!< denoised frame duration on the GPU in [ns] + time_point_ns beginFrame; //!< Renderer::beginFrame() time since epoch [ns] + time_point_ns endFrame; //!< Renderer::endFrame() time since epoch [ns] + time_point_ns backendBeginFrame; //!< Backend thread time of frame start since epoch [ns] + time_point_ns backendEndFrame; //!< Backend thread time of frame end since epoch [ns] + time_point_ns gpuFrameComplete; //!< GPU thread time of frame end since epoch [ns] or 0 + time_point_ns vsync; //!< VSYNC time of this frame since epoch [ns] + time_point_ns displayPresent; //!< Actual presentation time of this frame since epoch [ns] + time_point_ns presentDeadline; //!< deadline for queuing a frame [ns] + duration_ns displayPresentInterval; //!< display refresh rate [ns] + duration_ns compositionToPresentLatency; //!< time between the start of composition and the expected present time [ns] + time_point_ns expectedPresentTime; //!< system's expected presentation time since epoch [ns] + }; + + /** + * Retrieve a history of frame timing information. The maximum frame history size is + * given by getMaxFrameHistorySize(). + * All or part of the history can be lost when using a different SwapChain in beginFrame(). + * @param historySize requested history size. The returned vector could be smaller. + * @return A vector of FrameInfo. + * @see beginFrame() + */ + utils::FixedCapacityVector getFrameInfoHistory( + size_t historySize = 1) const noexcept; + + /** + * @return the maximum supported frame history size. + * @see getFrameInfoHistory() + */ + size_t getMaxFrameHistorySize() const noexcept; + /** * Use FrameRateOptions to set the desired frame rate and control how quickly the system * reacts to GPU load changes. @@ -228,9 +274,42 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { /** - * Set-up a frame for this Renderer. + * The use of this method is optional. It sets the VSYNC time expressed as the duration in + * nanosecond since epoch of std::chrono::steady_clock. + * If called, passing 0 to vsyncSteadyClockTimeNano in Renderer::BeginFrame will use this + * time instead. + * @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock + * @see Engine::getSteadyClockTimeNano() + * @see Renderer::BeginFrame() + */ + void setVsyncTime(uint64_t steadyClockTimeNano) noexcept; + + /** + * Call skipFrame when momentarily skipping frames, for instance if the content of the + * scene doesn't change. + * + * @param vsyncSteadyClockTimeNano + */ + void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u); + + /** + * Returns true if the current frame should be rendered. + * + * This is a convenience method that returns the same value as beginFrame(). + * + * @return + * *false* the current frame should be skipped, + * *true* the current frame can be rendered + * + * @see + * beginFrame() + */ + bool shouldRenderFrame() const noexcept; + + /** + * Set up a frame for this Renderer. * - * beginFrame() manages frame pacing, and returns whether or not a frame should be drawn. The + * beginFrame() manages frame-pacing, and returns whether a frame should be drawn. The * goal of this is to skip frames when the GPU falls behind in order to keep the frame * latency low. * @@ -249,6 +328,8 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * or 0 if unknown. This value should be the timestamp of * the last h/w vsync. It is expressed in the * std::chrono::steady_clock time base. + * On Android this should be the frame time received from + * a Choreographer. * @param swapChain A pointer to the SwapChain instance to use. * * @return @@ -260,6 +341,8 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * * @note * All calls to render() must happen *after* beginFrame(). + * It is recommended to use the same swapChain for every call to beginFrame, failing to do + * so can result is losing all or part of the FrameInfo history. * * @see * endFrame() @@ -456,7 +539,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * * Framebuffer as seen on User buffer (PixelBufferDescriptor&) * screen - * + * * +--------------------+ * | | .stride .alignment * | | ----------------------->--> @@ -486,6 +569,9 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * uploaded to it via setImage, the data returned from readPixels will be y-flipped with respect * to the setImage call. * + * Note: the texture that backs the COLOR attachment for `renderTarget` must have + * TextureUsage::BLIT_SRC as part of its usage. + * * @remark * readPixels() is intended for debugging and testing. It will impact performance significantly. * @@ -577,6 +663,19 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { */ void resetUserTime(); + + /** + * Requests the next frameCount frames to be skipped. For Debugging. + * @param frameCount number of frames to skip. + */ + void skipNextFrames(size_t frameCount) const noexcept; + + /** + * Remainder count of frame to be skipped + * @return remaining frames to be skipped + */ + size_t getFrameToSkipCount() const noexcept; + protected: // prevent heap allocation ~Renderer() = default; diff --git a/package/ios/libs/filament/include/filament/Scene.h b/package/ios/libs/filament/include/filament/Scene.h index 9df6285c..8758ab70 100644 --- a/package/ios/libs/filament/include/filament/Scene.h +++ b/package/ios/libs/filament/include/filament/Scene.h @@ -141,6 +141,11 @@ class UTILS_PUBLIC Scene : public FilamentAPI { */ void removeEntities(const utils::Entity* UTILS_NONNULL entities, size_t count); + /** + * Remove all entities to the Scene. + */ + void removeAllEntities() noexcept; + /** * Returns the total number of Entities in the Scene, whether alive or not. * @return Total number of Entities in the Scene. diff --git a/package/ios/libs/filament/include/filament/SkinningBuffer.h b/package/ios/libs/filament/include/filament/SkinningBuffer.h index 36ae30ed..c397f62b 100644 --- a/package/ios/libs/filament/include/filament/SkinningBuffer.h +++ b/package/ios/libs/filament/include/filament/SkinningBuffer.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -39,7 +40,7 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { struct BuilderDetails; public: - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -69,6 +70,33 @@ class UTILS_PUBLIC SkinningBuffer : public FilamentAPI { */ Builder& initialize(bool initialize = true) noexcept; + /** + * Associate an optional name with this SkinningBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this SkinningBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this SkinningBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this SkinningBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the SkinningBuffer object and returns a pointer to it. * diff --git a/package/ios/libs/filament/include/filament/Skybox.h b/package/ios/libs/filament/include/filament/Skybox.h index ce203aae..bacd0acb 100644 --- a/package/ios/libs/filament/include/filament/Skybox.h +++ b/package/ios/libs/filament/include/filament/Skybox.h @@ -131,6 +131,20 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { */ Builder& color(math::float4 color) noexcept; + /** + * Set the rendering priority of the Skybox. By default, it is set to the lowest + * priority (7) such that the Skybox is always rendered after the opaque objects, + * to reduce overdraw when depth culling is enabled. + * + * @param priority clamped to the range [0..7], defaults to 4; 7 is lowest priority + * (rendered last). + * + * @return Builder reference for chaining calls. + * + * @see RenderableManager::Builder::priority() + */ + Builder& priority(uint8_t priority) noexcept; + /** * Creates the Skybox object and returns a pointer to it. * diff --git a/package/ios/libs/filament/include/filament/Stream.h b/package/ios/libs/filament/include/filament/Stream.h index 6cafbacc..d3f914eb 100644 --- a/package/ios/libs/filament/include/filament/Stream.h +++ b/package/ios/libs/filament/include/filament/Stream.h @@ -23,6 +23,9 @@ #include #include +#include + +#include #include @@ -94,7 +97,7 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * * To create a NATIVE stream, call the
stream
method on the builder. */ - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -136,6 +139,33 @@ class UTILS_PUBLIC Stream : public FilamentAPI { */ Builder& height(uint32_t height) noexcept; + /** + * Associate an optional name with this Stream for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this Stream + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this Stream for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this Stream + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + /** * Creates the Stream object and returns a pointer to it. * @@ -174,9 +204,10 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * The callback tales two arguments: the AHardwareBuffer and the userdata. * @param userdata Optional closure data. Filament will pass this into the callback when it * releases the image. + * @param transform Optional transform matrix to apply to the image. */ void setAcquiredImage(void* UTILS_NONNULL image, - Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata) noexcept; + Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata, math::mat3f const& transform = math::mat3f()) noexcept; /** * @see setAcquiredImage(void*, Callback, void*) @@ -187,10 +218,11 @@ class UTILS_PUBLIC Stream : public FilamentAPI { * It callback tales two arguments: the AHardwareBuffer and the userdata. * @param userdata Optional closure data. Filament will pass this into the callback when it * releases the image. + * @param transform Optional transform matrix to apply to the image. */ void setAcquiredImage(void* UTILS_NONNULL image, backend::CallbackHandler* UTILS_NULLABLE handler, - Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata) noexcept; + Callback UTILS_NONNULL callback, void* UTILS_NULLABLE userdata, math::mat3f const& transform = math::mat3f()) noexcept; /** * Updates the size of the incoming stream. Whether this value is used is diff --git a/package/ios/libs/filament/include/filament/SwapChain.h b/package/ios/libs/filament/include/filament/SwapChain.h index 6917507a..94721c0d 100644 --- a/package/ios/libs/filament/include/filament/SwapChain.h +++ b/package/ios/libs/filament/include/filament/SwapChain.h @@ -21,7 +21,6 @@ #include #include -#include #include #include @@ -35,7 +34,7 @@ class Engine; /** * A swap chain represents an Operating System's *native* renderable surface. * - * Typically it's a native window or a view. Because a SwapChain is initialized from a + * Typically, it's a native window or a view. Because a SwapChain is initialized from a * native object, it is given to filament as a `void *`, which must be of the proper type * for each platform filament is running on. * @@ -158,7 +157,7 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { /** * Requests a SwapChain with an alpha channel. */ - static const uint64_t CONFIG_TRANSPARENT = backend::SWAP_CHAIN_CONFIG_TRANSPARENT; + static constexpr uint64_t CONFIG_TRANSPARENT = backend::SWAP_CHAIN_CONFIG_TRANSPARENT; /** * This flag indicates that the swap chain may be used as a source surface @@ -168,13 +167,13 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * @see * Renderer.copyFrame() */ - static const uint64_t CONFIG_READABLE = backend::SWAP_CHAIN_CONFIG_READABLE; + static constexpr uint64_t CONFIG_READABLE = backend::SWAP_CHAIN_CONFIG_READABLE; /** * Indicates that the native X11 window is an XCB window rather than an XLIB window. * This is ignored on non-Linux platforms and in builds that support only one X11 API. */ - static const uint64_t CONFIG_ENABLE_XCB = backend::SWAP_CHAIN_CONFIG_ENABLE_XCB; + static constexpr uint64_t CONFIG_ENABLE_XCB = backend::SWAP_CHAIN_CONFIG_ENABLE_XCB; /** * Indicates that the native window is a CVPixelBufferRef. @@ -186,7 +185,7 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * Filament. Filament will call CVPixelBufferRetain during Engine::createSwapChain, and * CVPixelBufferRelease when the swap chain is destroyed. */ - static const uint64_t CONFIG_APPLE_CVPIXELBUFFER = + static constexpr uint64_t CONFIG_APPLE_CVPIXELBUFFER = backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER; /** @@ -234,6 +233,21 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { */ static constexpr uint64_t CONFIG_PROTECTED_CONTENT = backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; + /** + * Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the + * given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is + * true. + * + * This is supported by EGL(Android) and Metal. Other GL platforms (GLX, WGL, etc) don't support + * it because the swapchain MSAA settings must be configured before window creation. + * + * With Metal, this flag should only be used when rendering a single View into a SwapChain. This + * flag is not supported when rendering multiple Filament Views into this SwapChain. + * + * @see isMSAASwapChainSupported(4) + */ + static constexpr uint64_t CONFIG_MSAA_4_SAMPLES = backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES; + /** * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag. * The default implementation returns false. @@ -252,56 +266,91 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { */ static bool isSRGBSwapChainSupported(Engine& engine) noexcept; + /** + * Return whether createSwapChain supports the CONFIG_MSAA_*_SAMPLES flag. + * The default implementation returns false. + * + * @param engine A pointer to the filament Engine + * @param samples The number of samples + * @return true if CONFIG_MSAA_*_SAMPLES is supported, false otherwise. + */ + static bool isMSAASwapChainSupported(Engine& engine, uint32_t samples) noexcept; + void* UTILS_NULLABLE getNativeWindow() const noexcept; /** - * FrameScheduledCallback is a callback function that notifies an application when Filament has - * completed processing a frame and that frame is ready to be scheduled for presentation. + * If this flag is passed to setFrameScheduledCallback, then the behavior of the default + * CallbackHandler (when nullptr is passed as the handler argument) is altered to call the + * callback on the Metal completion handler thread (as opposed to the main Filament thread). + * This flag also instructs the Metal backend to release the associated CAMetalDrawable on the + * completion handler thread. + * + * This flag has no effect if a custom CallbackHandler is passed or on backends other than Metal. + * + * @see setFrameScheduledCallback + */ + static constexpr uint64_t CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER = 1; + + /** + * FrameScheduledCallback is a callback function that notifies an application about the status + * of a frame after Filament has finished its processing. + * + * The exact timing and semantics of this callback differ depending on the graphics backend in + * use. + * + * Metal Backend + * ============= + * + * With the Metal backend, this callback signifies that Filament has completed all CPU-side + * processing for a frame and the frame is ready to be scheduled for presentation. * * Typically, Filament is responsible for scheduling the frame's presentation to the SwapChain. - * If a SwapChain::FrameScheduledCallback is set, however, the application bares the - * responsibility of scheduling a frame for presentation by calling the backend::PresentCallable - * passed to the callback function. Currently this functionality is only supported by the Metal - * backend. + * If a SwapChain::FrameScheduledCallback is set, however, the application bears the + * responsibility of scheduling the frame for presentation by calling the + * backend::PresentCallable passed to the callback function. In this mode, Filament will *not* + * automatically schedule the frame for presentation. * - * A FrameScheduledCallback can be set on an individual SwapChain through - * SwapChain::setFrameScheduledCallback. If the callback is set for a given frame, then the - * SwapChain will *not* automatically schedule itself for presentation. Instead, the application - * must call the PresentCallable passed to the FrameScheduledCallback. + * When using the Metal backend, if your application delays the call to the PresentCallable + * (e.g., by invoking it on a separate thread), you must ensure all PresentCallables have been + * called before shutting down the Filament Engine. You can guarantee this by calling + * Engine::flushAndWait() before Engine::shutdown(). This is necessary to ensure the Engine has + * a chance to clean up all memory related to frame presentation. * - * Each SwapChain can have only one FrameScheduledCallback set per frame. If - * setFrameScheduledCallback is called multiple times on the same SwapChain before - * Renderer::endFrame(), the most recent call effectively overwrites any previously set - * callback. This allows the callback to be updated as needed before the frame has finished - * encoding. + * Other Backends (OpenGL, Vulkan, WebGPU) + * ======================================= * - * The "last" callback set by setFrameScheduledCallback gets "latched" when Renderer::endFrame() - * is executed. At this point, the state of the callback is fixed and is the one used for the - * frame that was just encoded. Subsequent changes to the callback using - * setFrameScheduledCallback after endFrame() apply to the next frame. + * On other backends, this callback serves as a notification that Filament has completed all + * CPU-side processing for a frame. Filament proceeds with its normal presentation logic + * automatically, and the PresentCallable passed to the callback is a no-op that can be safely + * ignored. * - * Use \c setFrameScheduledCallback() (with default arguments) to unset the callback. + * General Behavior + * ================ * - * If your application delays the call to the PresentCallable by, for example, calling it on a - * separate thread, you must ensure all PresentCallables have been called before shutting down - * the Filament Engine. You can do this by issuing an Engine::flushAndWait before calling - * Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean - * up all memory related to frame presentation. + * A FrameScheduledCallback can be set on an individual SwapChain through + * SwapChain::setFrameScheduledCallback. Each SwapChain can have only one callback set per + * frame. If setFrameScheduledCallback is called multiple times on the same SwapChain before + * Renderer::endFrame(), the most recent call effectively overwrites any previously set + * callback. * - * @param handler Handler to dispatch the callback or nullptr for the default handler. - * @param callback Callback called when the frame is scheduled. + * The callback set by setFrameScheduledCallback is "latched" when Renderer::endFrame() is + * executed. At this point, the callback is fixed for the frame that was just encoded. + * Subsequent calls to setFrameScheduledCallback after endFrame() will apply to the next frame. + * + * Use \c setFrameScheduledCallback() (with default arguments) to unset the callback. * - * @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other - * backends ignore the callback (which will never be called) and proceed normally. + * @param handler Handler to dispatch the callback or nullptr for the default handler. + * @param callback Callback to be invoked when the frame processing is complete. + * @param flags See CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER * * @see CallbackHandler * @see PresentCallable */ void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, - FrameScheduledCallback&& callback = {}); + FrameScheduledCallback&& callback = {}, uint64_t flags = 0); /** - * Returns whether or not this SwapChain currently has a FrameScheduledCallback set. + * Returns whether this SwapChain currently has a FrameScheduledCallback set. * * @return true, if the last call to setFrameScheduledCallback set a callback * diff --git a/package/ios/libs/filament/include/filament/Sync.h b/package/ios/libs/filament/include/filament/Sync.h new file mode 100644 index 00000000..0398ced6 --- /dev/null +++ b/package/ios/libs/filament/include/filament/Sync.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_SYNC_H +#define TNT_FILAMENT_SYNC_H + +#include + +#include +#include +#include + +namespace filament { + +class UTILS_PUBLIC Sync : public FilamentAPI { +public: + using CallbackHandler = backend::CallbackHandler; + using Callback = backend::Platform::SyncCallback; + + /** + * Fetches a handle to the external, platform-specific representation of + * this sync object. + * + * @param handler A handler for the callback that will receive the handle + * @param callback A callback that will receive the handle when ready + * @param userData Data to be passed to the callback so that the application + * can identify what frame the sync is relevant to. + * @return The external handle for the Sync. This is valid destroy() is + * called on this Sync object. + */ + void getExternalHandle(CallbackHandler* handler, Callback callback, void* userData) noexcept; + +protected: + // prevent heap allocation + ~Sync() = default; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_SYNC_H diff --git a/package/ios/libs/filament/include/filament/Texture.h b/package/ios/libs/filament/include/filament/Texture.h index 8a27f831..f7d3fcbf 100644 --- a/package/ios/libs/filament/include/filament/Texture.h +++ b/package/ios/libs/filament/include/filament/Texture.h @@ -23,11 +23,15 @@ #include #include +#include #include +#include +#include #include +#include #include #include @@ -70,7 +74,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { struct BuilderDetails; public: - static constexpr const size_t BASE_LEVEL = 0; + static constexpr size_t BASE_LEVEL = 0; //! Face offsets for all faces of a cubemap struct FaceOffsets; @@ -84,35 +88,42 @@ class UTILS_PUBLIC Texture : public FilamentAPI { using CompressedType = backend::CompressedPixelDataType; //!< Compressed pixel data format using Usage = backend::TextureUsage; //!< Usage affects texel layout using Swizzle = backend::TextureSwizzle; //!< Texture swizzle + using ExternalImageHandle = backend::Platform::ExternalImageHandle; + using ExternalImageHandleRef = backend::Platform::ExternalImageHandleRef; + using AsyncCompletionCallback = + std::function; + using AsyncCallId = backend::AsyncCallId; - /** @return whether a backend supports a particular format. */ + /** @return Whether a backend supports a particular format. */ static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept; - /** @return whether this backend supports protected textures. */ + /** @return Whether a backend supports mipmapping of a particular format. */ + static bool isTextureFormatMipmappable(Engine& engine, InternalFormat format) noexcept; + + /** @return Whether particular format is compressed */ + static bool isTextureFormatCompressed(InternalFormat format) noexcept; + + /** @return Whether this backend supports protected textures. */ static bool isProtectedTexturesSupported(Engine& engine) noexcept; - /** @return whether a backend supports texture swizzling. */ + /** @return Whether a backend supports texture swizzling. */ static bool isTextureSwizzleSupported(Engine& engine) noexcept; - static size_t computeTextureDataSize(Texture::Format format, Texture::Type type, + static size_t computeTextureDataSize(Format format, Type type, size_t stride, size_t height, size_t alignment) noexcept; + /** @return Whether a combination of texture format, pixel format and type is valid. */ + static bool validatePixelFormatAndType(InternalFormat internalFormat, Format format, Type type) noexcept; - /** - * Options for environment prefiltering into reflection map - * - * @see generatePrefilterMipmap() - */ - struct PrefilterOptions { - uint16_t sampleCount = 8; //!< sample count used for filtering - bool mirror = true; //!< whether the environment must be mirrored - private: - UTILS_UNUSED uintptr_t reserved[3] = {}; - }; + /** @return the maximum size in texels of a texture of type \p type. At least 2048 for + * 2D textures, 256 for 3D textures. */ + static size_t getMaxTextureSize(Engine& engine, Sampler type) noexcept; + /** @return the maximum number of layers supported by texture arrays. At least 256. */ + static size_t getMaxArrayTextureLayers(Engine& engine) noexcept; //! Use Builder to construct a Texture object instance - class Builder : public BuilderBase { + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -156,6 +167,20 @@ class UTILS_PUBLIC Texture : public FilamentAPI { */ Builder& levels(uint8_t levels) noexcept; + /** + * Specifies the numbers of samples used for MSAA (Multisample Anti-Aliasing). + * + * Calling this method implicitly indicates the texture is used as a render target. Hence, + * this method should not be used in conjunction with other methods that are semantically + * conflicting like `setImage`. + * + * If this is invoked for array textures, it means this texture is used for multiview. + * + * @param samples Number of samples for this texture. + * @return This Builder, for chaining calls. + */ + Builder& samples(uint8_t samples) noexcept; + /** * Specifies the type of sampler to use. * @param target Sampler type @@ -202,6 +227,71 @@ class UTILS_PUBLIC Texture : public FilamentAPI { */ Builder& swizzle(Swizzle r, Swizzle g, Swizzle b, Swizzle a) noexcept; + /** + * Associate an optional name with this Texture for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this Texture + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this Texture for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this Texture + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + + /** + * Creates an external texture. The content must be set using setExternalImage(). + * The sampler can be SAMPLER_EXTERNAL or SAMPLER_2D depending on the format. Generally + * YUV formats must use SAMPLER_EXTERNAL. This depends on the backend features and is not + * validated. + * + * If the Sampler is set to SAMPLER_EXTERNAL, external() is implied. + * + * @return + */ + Builder& external() noexcept; + + /** + * Specifies a callback that will execute once the resource's data has been fully allocated + * within the GPU memory. This enables the resource creation process to be handled + * asynchronously. + * + * Any asynchronous calls made during a resource's asynchronous creation (using this method) + * are safe because they are queued and executed in sequence. However, invoking regular + * methods on the same resource before it's fully ready is unsafe and may cause undefined + * behavior. Users can call the `isCreationComplete()` method for the resource to confirm + * when the resource is ready for regular API calls. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * This method and the `external()` method are mutually exclusive. You cannot use both + * because external texture's contents are filled later by calling `setExternalImage()`. + * + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * @return This Builder, for chaining calls. + */ + Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback = nullptr, + void* UTILS_NULLABLE user = nullptr) noexcept; + /** * Creates the Texture object and returns a pointer to it. * @@ -332,7 +422,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * uint32_t width, uint32_t height, uint32_t depth, * PixelBufferDescriptor&& buffer) */ - inline void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const { + void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const { setImage(engine, level, 0, 0, 0, uint32_t(getWidth(level)), uint32_t(getHeight(level)), 1, std::move(buffer)); } @@ -345,7 +435,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * uint32_t width, uint32_t height, uint32_t depth, * PixelBufferDescriptor&& buffer) */ - inline void setImage(Engine& engine, size_t level, + void setImage(Engine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, PixelBufferDescriptor&& buffer) const { setImage(engine, level, xoffset, yoffset, 0, width, height, 1, std::move(buffer)); @@ -378,9 +468,108 @@ class UTILS_PUBLIC Texture : public FilamentAPI { void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const; + /** + * An asynchronous version of `setImage()`. + * Updates a sub-image of a 3D texture or 2D texture array for a level. Cubemaps are treated + * like a 2D array of six layers. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param engine Engine this texture is associated to. + * @param level Level to set the image for. + * @param xoffset Left offset of the sub-region to update. + * @param yoffset Bottom offset of the sub-region to update. + * @param zoffset Depth offset of the sub-region to update. + * @param width Width of the sub-region to update. + * @param height Height of the sub-region to update. + * @param depth Depth of the sub-region to update. + * @param buffer Client-side buffer containing the image to set. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + * + * @attention \p engine must be the instance passed to Builder::build() + * @attention \p level must be less than getLevels(). + * @attention \p buffer's Texture::Format must match that of getFormat(). + * @attention This Texture instance must use Sampler::SAMPLER_3D, Sampler::SAMPLER_2D_ARRAY + * or Sampler::SAMPLER_CUBEMAP. + * + * @see Builder::sampler() + */ + AsyncCallId setImageAsync(Engine& engine, size_t level, + uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + uint32_t width, uint32_t height, uint32_t depth, + PixelBufferDescriptor&& buffer, + backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr) const; + + /** + * inline helper to update a 2D texture asynchronously + * + * @see setImageAsync(Engine& engine, size_t level, + * uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + * uint32_t width, uint32_t height, uint32_t depth, + * PixelBufferDescriptor&& buffer, + * backend::CallbackHandler* UTILS_NULLABLE handler, + * AsyncCompletionCallback callback, void* user) + */ + AsyncCallId setImageAsync(Engine& engine, size_t level, PixelBufferDescriptor&& buffer, + backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr) const { + return setImageAsync(engine, level, 0, 0, 0, + uint32_t(getWidth(level)), uint32_t(getHeight(level)), 1, std::move(buffer), + handler, std::move(callback), user); + } + + /** + * inline helper to update a 2D texture asynchronously + * + * @see setImageAsync(Engine& engine, size_t level, + * uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + * uint32_t width, uint32_t height, uint32_t depth, + * PixelBufferDescriptor&& buffer, + * backend::CallbackHandler* UTILS_NULLABLE handler, + * AsyncCompletionCallback callback, void* user) + */ + AsyncCallId setImageAsync(Engine& engine, size_t level, + uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, + PixelBufferDescriptor&& buffer, + backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, + void* UTILS_NULLABLE user = nullptr) const { + return setImageAsync(engine, level, xoffset, yoffset, 0, width, height, 1, std::move(buffer), + handler, std::move(callback), user); + } + + /** + * Specify the external image to associate with this Texture. Typically, the external + * image is OS specific, and can be a video or camera frame. + * There are many restrictions when using an external image as a texture, such as: + * - only the level of detail (lod) 0 can be specified + * - only nearest or linear filtering is supported + * - the size and format of the texture is defined by the external image + * - only the CLAMP_TO_EDGE wrap mode is supported + * + * @param engine Engine this texture is associated to. + * @param image An opaque handle to a platform specific image. It must be created using Platform + * specific APIs. For example PlatformEGL::createExternalImage(EGLImageKHR eglImage) + * + * @see PlatformEGL::createExternalImage + * @see PlatformEGLAndroid::createExternalImage + * @see PlatformCocoaGL::createExternalImage + * @see PlatformCocoaTouchGL::createExternalImage + */ + void setExternalImage(Engine& engine, ExternalImageHandleRef image) noexcept; /** - * Specify the external image to associate with this Texture. Typically the external + * Specify the external image to associate with this Texture. Typically, the external * image is OS specific, and can be a video or camera frame. * There are many restrictions when using an external image as a texture, such as: * - only the level of detail (lod) 0 can be specified @@ -401,11 +590,13 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * * @see Builder::sampler() * + * @deprecated Instead, use setExternalImage(Engine& engine, ExternalImageHandleRef image) */ + UTILS_DEPRECATED void setExternalImage(Engine& engine, void* UTILS_NONNULL image) noexcept; /** - * Specify the external image and plane to associate with this Texture. Typically the external + * Specify the external image and plane to associate with this Texture. Typically, the external * image is OS specific, and can be a video or camera frame. When using this method, the * external image must be a planar type (such as a YUV camera frame). The plane parameter * selects which image plane is bound to this texture. @@ -436,7 +627,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { void setExternalImage(Engine& engine, void* UTILS_NONNULL image, size_t plane) noexcept; /** - * Specify the external stream to associate with this Texture. Typically the external + * Specify the external stream to associate with this Texture. Typically, the external * stream is OS specific, and can be a video or camera stream. * There are many restrictions when using an external stream as a texture, such as: * - only the level of detail (lod) 0 can be specified @@ -447,7 +638,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @param stream A Stream object * * @attention \p engine must be the instance passed to Builder::build() - * @attention This Texture instance must use Sampler::SAMPLER_EXTERNAL or it has no effect + * @attention This Texture instance must use Sampler::SAMPLER_EXTERNAL, or it has no effect * * @see Builder::sampler(), Stream * @@ -467,43 +658,16 @@ class UTILS_PUBLIC Texture : public FilamentAPI { void generateMipmaps(Engine& engine) const noexcept; /** - * Creates a reflection map from an environment map. - * - * This is a utility function that replaces calls to Texture::setImage(). - * The provided environment map is processed and all mipmap levels are populated. The - * processing is similar to the offline tool `cmgen` as a lower quality setting. - * - * This function is intended to be used when the environment cannot be processed offline, - * for instance if it's generated at runtime. - * - * The source data must obey to some constraints: - * - the data type must be PixelDataFormat::RGB - * - the data format must be one of - * - PixelDataType::FLOAT - * - PixelDataType::HALF - * - * The current texture must be a cubemap - * - * The reflections cubemap's internal format cannot be a compressed format. - * - * The reflections cubemap's dimension must be a power-of-two. + * This non-blocking method checks if the resource has finished creation. If the resource + * creation was initiated asynchronously, it will return true only after all related + * asynchronous tasks are complete. If the resource was created normally without using async + * method, it will always return true. * - * @warning This operation is computationally intensive, especially with large environments and - * is currently synchronous. Expect about 1ms for a 16x16 cubemap. - * - * @param engine Reference to the filament::Engine to associate this IndirectLight with. - * @param buffer Client-side buffer containing the images to set. - * @param faceOffsets Offsets in bytes into \p buffer for all six images. The offsets - * are specified in the following order: +x, -x, +y, -y, +z, -z - * @param options Optional parameter to controlling user-specified quality and options. - * - * @exception utils::PreConditionPanic if the source data constraints are not respected. + * @return Whether the resource is created. * + * @see Builder::async() */ - void generatePrefilterMipmap(Engine& engine, - PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets, - PrefilterOptions const* UTILS_NULLABLE options = nullptr); - + bool isCreationComplete() const noexcept; /** @deprecated */ struct FaceOffsets { diff --git a/package/ios/libs/filament/include/filament/TextureSampler.h b/package/ios/libs/filament/include/filament/TextureSampler.h index ba5e5534..ea40a705 100644 --- a/package/ios/libs/filament/include/filament/TextureSampler.h +++ b/package/ios/libs/filament/include/filament/TextureSampler.h @@ -154,8 +154,8 @@ class UTILS_PUBLIC TextureSampler { /** * This controls anisotropic filtering. - * @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 0. - * The maximum permissible value is 7. + * @param anisotropy Amount of anisotropy, should be a power-of-two. The default is 1. + * The maximum permissible value is 128. */ void setAnisotropy(float anisotropy) noexcept { const int log2 = ilogbf(anisotropy > 0 ? anisotropy : -anisotropy); diff --git a/package/ios/libs/filament/include/filament/ToneMapper.h b/package/ios/libs/filament/include/filament/ToneMapper.h index e89e702c..cff0576e 100644 --- a/package/ios/libs/filament/include/filament/ToneMapper.h +++ b/package/ios/libs/filament/include/filament/ToneMapper.h @@ -68,6 +68,21 @@ struct UTILS_PUBLIC ToneMapper { * function applied ("linear") */ virtual math::float3 operator()(math::float3 c) const noexcept = 0; + + /** + * If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b)) + * + * This may be used to indicate that the color grading's LUT only requires a 1D texture instead + * of a 3D texture, potentially saving a significant amount of memory and generation time. + */ + virtual bool isOneDimensional() const noexcept { return false; } + + /** + * True if this tonemapper only works in low-dynamic-range. + * + * This may be used to indicate that the color grading's LUT doesn't need to be log encoded. + */ + virtual bool isLDR() const noexcept { return false; } }; /** @@ -79,6 +94,8 @@ struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper { ~LinearToneMapper() noexcept final; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return true; } + bool isLDR() const noexcept override { return true; } }; /** @@ -91,6 +108,8 @@ struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper { ~ACESToneMapper() noexcept final; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; /** @@ -104,6 +123,8 @@ struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper { ~ACESLegacyToneMapper() noexcept final; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; /** @@ -117,6 +138,8 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper { ~FilmicToneMapper() noexcept final; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return true; } + bool isLDR() const noexcept override { return false; } }; /** @@ -129,6 +152,8 @@ struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper { ~PBRNeutralToneMapper() noexcept final; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; /** @@ -150,6 +175,8 @@ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper { ~AgxToneMapper() noexcept final; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } AgxLook look; }; @@ -194,6 +221,8 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper { GenericToneMapper& operator=(GenericToneMapper&& rhs) noexcept; math::float3 operator()(math::float3 x) const noexcept override; + bool isOneDimensional() const noexcept override { return true; } + bool isLDR() const noexcept override { return false; } /** Returns the contrast of the curve as a strictly positive value. */ float getContrast() const noexcept; @@ -256,6 +285,8 @@ struct UTILS_PUBLIC DisplayRangeToneMapper final : public ToneMapper { ~DisplayRangeToneMapper() noexcept override; math::float3 operator()(math::float3 c) const noexcept override; + bool isOneDimensional() const noexcept override { return false; } + bool isLDR() const noexcept override { return false; } }; } // namespace filament diff --git a/package/ios/libs/filament/include/filament/VertexBuffer.h b/package/ios/libs/filament/include/filament/VertexBuffer.h index fccbd004..b29c7f9a 100644 --- a/package/ios/libs/filament/include/filament/VertexBuffer.h +++ b/package/ios/libs/filament/include/filament/VertexBuffer.h @@ -26,7 +26,9 @@ #include #include +#include +#include #include #include @@ -60,8 +62,12 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { public: using AttributeType = backend::ElementType; using BufferDescriptor = backend::BufferDescriptor; + using AsyncCompletionCallback = + std::function; + using AsyncCallId = backend::AsyncCallId; - class Builder : public BuilderBase { + + class Builder : public BuilderBase, public BuilderNameMixin { friend struct BuilderDetails; public: Builder() noexcept; @@ -158,6 +164,56 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { */ Builder& advancedSkinning(bool enabled) noexcept; + /** + * Associate an optional name with this VertexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. The name is + * truncated to a maximum of 128 characters. + * + * The name string is copied during this method so clients may free its memory after + * the function returns. + * + * @param name A string to identify this VertexBuffer + * @param len Length of name, should be less than or equal to 128 + * @return This Builder, for chaining calls. + * @deprecated Use name(utils::StaticString const&) instead. + */ + UTILS_DEPRECATED + Builder& name(const char* UTILS_NONNULL name, size_t len) noexcept; + + /** + * Associate an optional name with this VertexBuffer for debugging purposes. + * + * name will show in error messages and should be kept as short as possible. + * + * @param name A string literal to identify this VertexBuffer + * @return This Builder, for chaining calls. + */ + Builder& name(utils::StaticString const& name) noexcept; + + /** + * Specifies a callback that will execute once the resource's data has been fully allocated + * within the GPU memory. This enables the resource creation process to be handled + * asynchronously. + * + * Any asynchronous calls made during a resource's asynchronous creation (using this method) + * are safe because they are queued and executed in sequence. However, invoking regular + * methods on the same resource before it's fully ready is unsafe and may cause undefined + * behavior. Users can call the `isCreationComplete()` method for the resource to confirm + * when the resource is ready for regular API calls. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * @return This Builder, for chaining calls. + */ + Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback = nullptr, + void* UTILS_NULLABLE user = nullptr) noexcept; + /** * Creates the VertexBuffer object and returns a pointer to it. * @@ -182,7 +238,7 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { size_t getVertexCount() const noexcept; /** - * Asynchronously copy-initializes the specified buffer from the given buffer data. + * copy-initializes the specified buffer from the given buffer data. * * Do not use this if you called enableBufferObjects() on the Builder. * @@ -193,11 +249,41 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { * index \p bufferIndex. BufferDescriptor points to raw, untyped data that will * be copied as-is into the buffer. * @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex - * buffer set. + * buffer set. Must be multiple of 4. */ void setBufferAt(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + /** + * An asynchronous version of `setBufferAt()`. + * Asynchronously copy-initializes the specified buffer from the given buffer data. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * Do not use this if you called enableBufferObjects() on the Builder. + * + * @param engine Reference to the filament::Engine to associate this VertexBuffer with. + * @param bufferIndex Index of the buffer to initialize. Must be between 0 + * and Builder::bufferCount() - 1. + * @param buffer A BufferDescriptor representing the data used to initialize the buffer at + * index \p bufferIndex. BufferDescriptor points to raw, untyped data that will + * be copied as-is into the buffer. + * @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex + * buffer set. Must be multiple of 4. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + */ + AsyncCallId setBufferAtAsync(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer, + uint32_t byteOffset, backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, void* UTILS_NULLABLE user = nullptr); + /** * Swaps in the given buffer object. * @@ -211,6 +297,45 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { void setBufferObjectAt(Engine& engine, uint8_t bufferIndex, BufferObject const* UTILS_NONNULL bufferObject); + /** + * An asynchronous version of `setBufferObjectAt()`. + * Swaps in the given buffer object. + * + * Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the + * asynchronous call. + * + * To use this method, the engine must be configured for asynchronous operation. Otherwise, + * calling async method will cause the program to terminate. + * + * To use this, you must first call enableBufferObjects() on the Builder. + * + * @param engine Reference to the filament::Engine to associate this VertexBuffer with. + * @param bufferIndex Index of the buffer to initialize. Must be between 0 + * and Builder::bufferCount() - 1. + * @param bufferObject The handle to the GPU data that will be used in this buffer slot. + * @param handler Handler to dispatch the callback or nullptr for the default handler + * @param callback A function to be called upon the completion of an asynchronous creation. + * @param user The custom data that will be passed as the second argument to the `callback`. + * + * @return An ID that the caller can use to cancel the operation. + */ + AsyncCallId setBufferObjectAtAsync(Engine& engine, uint8_t bufferIndex, + BufferObject const* UTILS_NONNULL bufferObject, + backend::CallbackHandler* UTILS_NULLABLE handler, + AsyncCompletionCallback callback, void* UTILS_NULLABLE user = nullptr); + + /** + * This non-blocking method checks if the resource has finished creation. If the resource + * creation was initiated asynchronously, it will return true only after all related + * asynchronous tasks are complete. If the resource was created normally without using async + * method, it will always return true. + * + * @return Whether the resource is created. + * + * @see Builder::async() + */ + bool isCreationComplete() const noexcept; + protected: // prevent heap allocation ~VertexBuffer() = default; diff --git a/package/ios/libs/filament/include/filament/View.h b/package/ios/libs/filament/include/filament/View.h index 1a1ed96f..0240bafa 100644 --- a/package/ios/libs/filament/include/filament/View.h +++ b/package/ios/libs/filament/include/filament/View.h @@ -24,8 +24,10 @@ #include #include +#include #include +#include #include @@ -40,6 +42,7 @@ class CallbackHandler; class Camera; class ColorGrading; +class Engine; class MaterialInstance; class RenderTarget; class Scene; @@ -182,12 +185,13 @@ class UTILS_PUBLIC View : public FilamentAPI { * View.\n * The View doesn't take ownership of the Camera pointer (which * acts as a reference). + * If the camera isn't set, Renderer::render() will result in a no-op. * * @note * There is no reference-counting. * Make sure to dissociate a Camera from all Views before destroying it. */ - void setCamera(Camera* UTILS_NONNULL camera) noexcept; + void setCamera(Camera* UTILS_NULLABLE camera) noexcept; /** * Returns whether a Camera is set. @@ -198,18 +202,38 @@ class UTILS_PUBLIC View : public FilamentAPI { /** * Returns the Camera currently associated with this View. - * @return A reference to the Camera associated to this View. + * Undefined behavior if hasCamera() is false. + * @return A reference to the Camera associated to this View if hasCamera() is true. + * @see hasCamera() */ Camera& getCamera() noexcept; /** * Returns the Camera currently associated with this View. + * Undefined behavior if hasCamera() is false. * @return A reference to the Camera associated to this View. + * @see hasCamera() */ Camera const& getCamera() const noexcept { return const_cast(this)->getCamera(); } + + /** + * Sets whether a channel must clear the depth buffer before all primitives are rendered. + * Channel depth clear is off by default for all channels. + * This is orthogonal to Renderer::setClearOptions(). + * @param channel between 0 and 7 + * @param enabled true to enable clear, false to disable + */ + void setChannelDepthClearEnabled(uint8_t channel, bool enabled) noexcept; + + /** + * @param channel between 0 and 7 + * @return true if this channel has depth clear enabled. + */ + bool isChannelDepthClearEnabled(uint8_t channel) const noexcept; + /** * Sets the blending mode used to draw the view into the SwapChain. * @@ -517,6 +541,14 @@ class UTILS_PUBLIC View : public FilamentAPI { */ DynamicResolutionOptions getDynamicResolutionOptions() const noexcept; + /** + * Returns the last dynamic resolution scale factor used by this view. This value is updated + * when Renderer::render(View*) is called + * @return a float2 where x is the horizontal and y the vertical scale factor. + * @see Renderer::render + */ + math::float2 getLastDynamicResolutionScale() const noexcept; + /** * Sets the rendering quality for this view. Refer to RenderQuality for more * information about the different settings available. @@ -540,12 +572,13 @@ class UTILS_PUBLIC View : public FilamentAPI { * visible -- in this case, using a larger value can improve performance. * e.g. when standing and looking straight, several meters of the ground * isn't visible and if lights are expected to shine there, there is no - * point using a short zLightNear. (Default 5m). + * point using a short zLightNear. This value is clamped between + * the camera near and far plane. (Default 5m). * * @param zLightFar Distance from the camera after which lights are not expected to be visible. * Similarly to zLightNear, setting this value properly can improve - * performance. (Default 100m). - * + * performance. This value is clamped between the camera near and far plane. + * (Default 100m). * * Together zLightNear and zLightFar must be chosen so that the visible influence of lights * is spread between these two values. @@ -568,6 +601,13 @@ class UTILS_PUBLIC View : public FilamentAPI { */ void setShadowType(ShadowType shadow) noexcept; + /** + * Returns the shadow mapping technique used by this View. + * + * @return value set by setShadowType(). + */ + ShadowType getShadowType() const noexcept; + /** * Sets VSM shadowing options that apply across the entire View. * @@ -659,6 +699,26 @@ class UTILS_PUBLIC View : public FilamentAPI { */ bool isFrontFaceWindingInverted() const noexcept; + /** + * Enables or disables transparent picking. Disabled by default. + * + * When transparent picking is enabled, View::pick() will pick from both + * transparent and opaque renderables. When disabled, View::pick() will only + * pick from opaque renderables. + * + * @param enabled true enables transparent picking, false disables it. + * + * @note Transparent picking will create an extra pass for rendering depth + * from both transparent and opaque renderables. + */ + void setTransparentPickingEnabled(bool enabled) noexcept; + + /** + * Returns true if transparent picking is enabled. + * See setTransparentPickingEnabled() for more information. + */ + bool isTransparentPickingEnabled() const noexcept; + /** * Enables use of the stencil buffer. * @@ -726,8 +786,31 @@ class UTILS_PUBLIC View : public FilamentAPI { void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept; //! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing. - Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept; + utils::FixedCapacityVector getDirectionalShadowCameras() const noexcept; + + //! debugging: enable or disable froxel visualisation for this view. + void setFroxelVizEnabled(bool enabled) noexcept; + + //! debugging: returns information about the froxel configuration + struct FroxelConfigurationInfo { + uint16_t width; + uint16_t height; + uint16_t depth; + uint32_t viewportWidth; + uint32_t viewportHeight; + math::uint2 froxelDimension; + float zLightFar; + float linearizer; + math::mat4f p; + math::float4 clipTransform; + }; + + struct FroxelConfigurationInfoWithAge { + FroxelConfigurationInfo info; + uint32_t age; + }; + FroxelConfigurationInfoWithAge getFroxelConfigurationInfo() const noexcept; /** Result of a picking query */ struct PickingQueryResult { @@ -878,6 +961,17 @@ class UTILS_PUBLIC View : public FilamentAPI { */ 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/package/ios/libs/filament/include/geometry/TangentSpaceMesh.h b/package/ios/libs/filament/include/geometry/TangentSpaceMesh.h index 980e81ac..1872aaad 100644 --- a/package/ios/libs/filament/include/geometry/TangentSpaceMesh.h +++ b/package/ios/libs/filament/include/geometry/TangentSpaceMesh.h @@ -340,7 +340,7 @@ class TangentSpaceMesh { std::is_same::value || std::is_same::value>::type; template> - void getAux(AuxAttribute attribute, T* out, size_t stride = 0) const noexcept; + void getAux(AuxAttribute attribute, T* out, size_t stride = 0) const; /** * Get number of output triangles. diff --git a/package/ios/libs/filament/include/gltfio/MaterialProvider.h b/package/ios/libs/filament/include/gltfio/MaterialProvider.h index f62d667b..e4cf01b3 100644 --- a/package/ios/libs/filament/include/gltfio/MaterialProvider.h +++ b/package/ios/libs/filament/include/gltfio/MaterialProvider.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -93,10 +94,18 @@ struct alignas(4) MaterialKey { bool hasSheen : 1; bool hasIOR : 1; bool hasVolume : 1; - uint8_t padding : 5; + bool hasDispersion : 1; + bool hasSpecular : 1; + bool hasSpecularTexture : 1; + bool hasSpecularColorTexture : 1; + bool padding : 1; + // -- 32 bit boundary -- + uint8_t specularTextureUV; + uint8_t specularColorTextureUV; + uint16_t padding2; }; -static_assert(sizeof(MaterialKey) == 16, "MaterialKey has unexpected size."); +static_assert(sizeof(MaterialKey) == 20, "MaterialKey has unexpected size."); UTILS_WARNING_POP @@ -189,6 +198,7 @@ void processShaderString(std::string* shader, const UvMap& uvmap, * Creates a material provider that builds materials on the fly, composing GLSL at run time. * * @param optimizeShaders Optimizes shaders, but at significant cost to construction time. + * @param variantFilters Filter out variants that are not required. * @return New material provider that can build materials at run time. * * Requires \c libfilamat to be linked in. Not available in \c libgltfio_core. @@ -196,7 +206,8 @@ void processShaderString(std::string* shader, const UvMap& uvmap, * @see createUbershaderProvider */ UTILS_PUBLIC -MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders = false); +MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders = false, + utils::FixedCapacityVector const& variantFilters = {}); /** * Creates a material provider that loads a small set of pre-built materials. diff --git a/package/ios/libs/filament/include/gltfio/TextureProvider.h b/package/ios/libs/filament/include/gltfio/TextureProvider.h index ff497af2..0015c86a 100644 --- a/package/ios/libs/filament/include/gltfio/TextureProvider.h +++ b/package/ios/libs/filament/include/gltfio/TextureProvider.h @@ -179,6 +179,19 @@ TextureProvider* createStbProvider(filament::Engine* engine); */ TextureProvider* createKtx2Provider(filament::Engine* engine); +/** + * If webp support is enabled at build time, creates a decoder that can handle "image/webp" + * lossless and lossy content. + * If webp support is not enabled at build time, returns nullptr. + */ +TextureProvider* createWebpProvider(filament::Engine* engine); + +/** + * Indicates if build-time webp support was included. + * Returns true if it was and false if not. + */ +bool isWebpSupported(); + } // namespace filament::gltfio template<> struct utils::EnableBitMaskOperators diff --git a/package/ios/libs/filament/include/gltfio/materials/uberarchive.h b/package/ios/libs/filament/include/gltfio/materials/uberarchive.h index b36ddd36..e56d0a02 100644 --- a/package/ios/libs/filament/include/gltfio/materials/uberarchive.h +++ b/package/ios/libs/filament/include/gltfio/materials/uberarchive.h @@ -5,9 +5,10 @@ extern "C" { extern const uint8_t UBERARCHIVE_PACKAGE[]; - extern int UBERARCHIVE_DEFAULT_OFFSET; - extern int UBERARCHIVE_DEFAULT_SIZE; } + +#define UBERARCHIVE_DEFAULT_OFFSET 0 +#define UBERARCHIVE_DEFAULT_SIZE 753712 #define UBERARCHIVE_DEFAULT_DATA (UBERARCHIVE_PACKAGE + UBERARCHIVE_DEFAULT_OFFSET) #endif diff --git a/package/ios/libs/filament/include/ibl/CubemapIBL.h b/package/ios/libs/filament/include/ibl/CubemapIBL.h index 925e27c0..ae8fcb08 100644 --- a/package/ios/libs/filament/include/ibl/CubemapIBL.h +++ b/package/ios/libs/filament/include/ibl/CubemapIBL.h @@ -54,7 +54,7 @@ class UTILS_PUBLIC CubemapIBL { * @param updater a callback for the caller to track progress */ static void roughnessFilter( - utils::JobSystem& js, Cubemap& dst, const utils::Slice& levels, + utils::JobSystem& js, Cubemap& dst, utils::Slice levels, float linearRoughness, size_t maxNumSamples, math::float3 mirror, bool prefilter, Progress updater = nullptr, void* userdata = nullptr); diff --git a/package/ios/libs/filament/include/math/TQuatHelpers.h b/package/ios/libs/filament/include/math/TQuatHelpers.h index 0439b971..7ff641b0 100644 --- a/package/ios/libs/filament/include/math/TQuatHelpers.h +++ b/package/ios/libs/filament/include/math/TQuatHelpers.h @@ -259,10 +259,11 @@ class TQuatFunctions { return normalize(lerp(d < 0 ? -p : p, q, t)); } const T npq = std::sqrt(dot(p, p) * dot(q, q)); // ||p|| * ||q|| - const T a = std::acos(filament::math::clamp(absd / npq, T(-1), T(1))); + const T cos_a = math::clamp(absd / npq, T(-1), T(1)); + const T a = std::acos(cos_a); const T a0 = a * (1 - t); const T a1 = a * t; - const T sina = sin(a); + const T sina = std::sqrt(T(1) - cos_a * cos_a); if (sina < value_eps) { return normalize(lerp(p, q, t)); } diff --git a/package/ios/libs/filament/include/math/compiler.h b/package/ios/libs/filament/include/math/compiler.h index 634e2077..a7d85168 100644 --- a/package/ios/libs/filament/include/math/compiler.h +++ b/package/ios/libs/filament/include/math/compiler.h @@ -122,6 +122,11 @@ struct is_arithmetic : std::integral_constant::value || std::is_floating_point::value> { }; +template +struct is_floating_point : std::integral_constant::value> { +}; + } // filament::math #endif // TNT_MATH_COMPILER_H diff --git a/package/ios/libs/filament/include/math/fast.h b/package/ios/libs/filament/include/math/fast.h index 85b990d2..9988cfd2 100644 --- a/package/ios/libs/filament/include/math/fast.h +++ b/package/ios/libs/filament/include/math/fast.h @@ -50,7 +50,7 @@ constexpr T MATH_PURE cos(T x) noexcept { // x between -pi and pi template::value>> constexpr T MATH_PURE sin(T x) noexcept { - return filament::math::fast::cos(x - T(F_PI_2)); + return fast::cos(x - T(F_PI_2)); } constexpr inline float MATH_PURE ilog2(float x) noexcept { diff --git a/package/ios/libs/filament/include/math/half.h b/package/ios/libs/filament/include/math/half.h index d792e1bf..4578708b 100644 --- a/package/ios/libs/filament/include/math/half.h +++ b/package/ios/libs/filament/include/math/half.h @@ -164,20 +164,20 @@ inline constexpr half operator""_h(long double v) { return half( static_cast(v) ); } -template<> struct is_arithmetic : public std::true_type {}; +template<> struct is_arithmetic : public std::true_type {}; + +template<> struct is_floating_point : public std::true_type {}; } // namespace math } // namespace filament namespace std { -template<> struct is_floating_point : public std::true_type {}; - -// note: this shouldn't be needed (is_floating_point<> is enough) but some version of msvc need it -// This stopped working with MSVC 2019 16.4, so we specialize our own version of is_arithmetic in +// Remove the standard template specializations for filament::math::half +// Clang 20 explicitly blocks customizing standard type traits +// Instead, use the traits defined in the math:: namespace +// This avoids compatibility issues with Clang 20 and MSVC 2019 16.4+ // the math::filament namespace (see above). -template<> struct is_arithmetic : public std::true_type {}; - template<> class numeric_limits { public: diff --git a/package/ios/libs/filament/include/math/mat2.h b/package/ios/libs/filament/include/math/mat2.h index dba9ca47..d12aa532 100644 --- a/package/ios/libs/filament/include/math/mat2.h +++ b/package/ios/libs/filament/include/math/mat2.h @@ -225,7 +225,7 @@ class MATH_EMPTY_BASES TMat22 : * Rotate by radians in the 2D plane */ static TMat22 rotate(T radian) noexcept { - TMat22 r(TMat22::NO_INIT); + TMat22 r(NO_INIT); T c = std::cos(radian); T s = std::sin(radian); r[0][0] = c; diff --git a/package/ios/libs/filament/include/math/mat3.h b/package/ios/libs/filament/include/math/mat3.h index 035865fe..57ad7ec3 100644 --- a/package/ios/libs/filament/include/math/mat3.h +++ b/package/ios/libs/filament/include/math/mat3.h @@ -256,7 +256,7 @@ class MATH_EMPTY_BASES TMat33 : */ friend inline constexpr TMat33 orthogonalize(const TMat33& m) noexcept { - TMat33 ret(TMat33::NO_INIT); + TMat33 ret(NO_INIT); ret[0] = normalize(m[0]); ret[2] = normalize(cross(ret[0], m[1])); ret[1] = normalize(cross(ret[2], ret[0])); @@ -479,7 +479,7 @@ constexpr TQuaternion TMat33::packTangentFrame(const TMat33& m, size_t template constexpr details::TMat33 prescaleForNormals(const details::TMat33& m) noexcept { return m * details::TMat33( - 1.0 / std::sqrt(max(float3{length2(m[0]), length2(m[1]), length2(m[2])}))); + T(1.0) / std::sqrt(max(float3{length2(m[0]), length2(m[1]), length2(m[2])}))); } // ---------------------------------------------------------------------------------------- diff --git a/package/ios/libs/filament/include/math/mat4.h b/package/ios/libs/filament/include/math/mat4.h index 57058539..7eb50e35 100644 --- a/package/ios/libs/filament/include/math/mat4.h +++ b/package/ios/libs/filament/include/math/mat4.h @@ -498,10 +498,10 @@ constexpr TMat44 TMat44::frustum(T left, T right, T bottom, T top, T near, } template -TMat44 TMat44::perspective(T fov, T aspect, T near, T far, TMat44::Fov direction) noexcept { +TMat44 TMat44::perspective(T fov, T aspect, T near, T far, Fov direction) noexcept { T h, w; - if (direction == TMat44::Fov::VERTICAL) { + if (direction == Fov::VERTICAL) { h = std::tan(fov * F_PI / 360.0f) * near; w = h * aspect; } else { diff --git a/package/ios/libs/filament/include/private/backend/VirtualMachineEnv.h b/package/ios/libs/filament/include/private/backend/VirtualMachineEnv.h new file mode 100644 index 00000000..c4352391 --- /dev/null +++ b/package/ios/libs/filament/include/private/backend/VirtualMachineEnv.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H +#define TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H + +#include +#include + +#include + +namespace filament { + +class VirtualMachineEnv { +public: + // must be called before VirtualMachineEnv::get() from a thread that is attached to the JavaVM + static jint JNI_OnLoad(JavaVM* vm); + + // must be called on backend thread + static VirtualMachineEnv& get() noexcept; + + // can be called from any thread that already has a JniEnv + static JNIEnv* getThreadEnvironment(); + + // must be called from the backend thread + JNIEnv* getEnvironment() noexcept { + JNIEnv* env = mJniEnv; + if (UTILS_UNLIKELY(!env)) { + return getEnvironmentSlow(); + } + return env; + } + + static void handleException(JNIEnv* env) noexcept; + +private: + explicit VirtualMachineEnv(JavaVM* vm) noexcept; + ~VirtualMachineEnv() noexcept; + JNIEnv* getEnvironmentSlow(); + + static utils::Mutex sLock; + static JavaVM* sVirtualMachine; + static JavaVM* getVirtualMachine(); + + JNIEnv* mJniEnv = nullptr; + JavaVM* mVirtualMachine = nullptr; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H diff --git a/package/ios/libs/filament/include/uberz/WritableArchive.h b/package/ios/libs/filament/include/uberz/WritableArchive.h index e511d95f..b383b70c 100644 --- a/package/ios/libs/filament/include/uberz/WritableArchive.h +++ b/package/ios/libs/filament/include/uberz/WritableArchive.h @@ -56,7 +56,7 @@ class WritableArchive { utils::FixedCapacityVector package; Shading shadingModel; BlendingMode blendingMode; - tsl::robin_map flags; + tsl::robin_map flags; }; utils::FixedCapacityVector mMaterials; diff --git a/package/ios/libs/filament/include/utils/Allocator.h b/package/ios/libs/filament/include/utils/Allocator.h index 7b9978f5..d74b8cc8 100644 --- a/package/ios/libs/filament/include/utils/Allocator.h +++ b/package/ios/libs/filament/include/utils/Allocator.h @@ -18,7 +18,6 @@ #define TNT_UTILS_ALLOCATOR_H #include -#include #include #include @@ -36,22 +35,22 @@ namespace utils { namespace pointermath { -template -static inline P* add(P* a, T b) noexcept { - return (P*)(uintptr_t(a) + uintptr_t(b)); +template +static P* add(P* a, T b) noexcept { + return (P*) (uintptr_t(a) + uintptr_t(b)); } -template -static inline P* align(P* p, size_t alignment) noexcept { +template +static P* align(P* p, size_t alignment) noexcept { // alignment must be a power-of-two - assert_invariant(alignment && !(alignment & alignment-1)); - return (P*)((uintptr_t(p) + alignment - 1) & ~(alignment - 1)); + assert(alignment && !(alignment & alignment-1)); + return (P*) ((uintptr_t(p) + alignment - 1) & ~(alignment - 1)); } -template -static inline P* align(P* p, size_t alignment, size_t offset) noexcept { +template +static P* align(P* p, size_t alignment, size_t offset) noexcept { P* const r = align(add(p, offset), alignment); - assert_invariant(r >= add(p, offset)); + assert(r >= add(p, offset)); return r; } @@ -102,7 +101,7 @@ class LinearAllocator { // free memory back to the specified point void rewind(void* p) UTILS_RESTRICT noexcept { - assert_invariant(p >= mBegin && p < end()); + assert(p >= mBegin && p < end()); set_current(p); } @@ -194,7 +193,7 @@ class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocat } ~LinearAllocatorWithFallback() noexcept { - LinearAllocatorWithFallback::reset(); + reset(); } void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)); @@ -204,7 +203,7 @@ class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocat } void rewind(void* p) noexcept { - if (p >= LinearAllocator::base() && p < LinearAllocator::end()) { + if (p >= base() && p < end()) { LinearAllocator::rewind(p); } } @@ -214,7 +213,7 @@ class LinearAllocatorWithFallback : private LinearAllocator, private HeapAllocat void free(void*, size_t) noexcept { } bool isHeapAllocation(void* p) const noexcept { - return p < LinearAllocator::base() || p >= LinearAllocator::end(); + return p < base() || p >= end(); } }; @@ -233,15 +232,16 @@ class FreeList { Node* const head = mHead; mHead = head ? head->next : nullptr; // this could indicate a use after free - assert_invariant(!mHead || mHead >= mBegin && mHead < mEnd); + assert(!mHead || mHead >= mBegin && mHead < mEnd); return head; } void push(void* p) noexcept { - assert_invariant(p); - assert_invariant(p >= mBegin && p < mEnd); - // TODO: assert this is one of our pointer (i.e.: it's address match one of ours) - Node* const head = static_cast(p); + assert(p); + assert(p >= mBegin && p < mEnd); + // we use placement-new to properly manage the lifetime + // this is noop already under O1 + Node* const head = new (p) Node; head->next = mHead; mHead = head; } @@ -278,41 +278,50 @@ class AtomicFreeList { void* pop() noexcept { Node* const pStorage = mStorage; - HeadPtr currentHead = mHead.load(); + HeadPtr currentHead = mHead.load(std::memory_order_relaxed); while (currentHead.offset >= 0) { // The value of "pNext" we load here might already contain application data if another // thread raced ahead of us. But in that case, the computed "newHead" will be discarded - // since compare_exchange_weak fails. Then this thread will loop with the updated + // since compare_exchange_weak() fails. Then this thread will loop with the updated // value of currentHead, and try again. - Node* const pNext = pStorage[currentHead.offset].next.load(std::memory_order_relaxed); + // TSAN complains if we don't use a local variable here. + Node const node = pStorage[currentHead.offset]; + Node const* const pNext = node.next; const HeadPtr newHead{ pNext ? int32_t(pNext - pStorage) : -1, currentHead.tag + 1 }; - // In the rare case that the other thread that raced ahead of us already returned the - // same mHead we just loaded, but it now has a different "next" value, the tag field will not - // match, and compare_exchange_weak will fail and prevent that particular race condition. - if (mHead.compare_exchange_weak(currentHead, newHead)) { + // In the rare case that the other thread that raced ahead of us already returned the + // same mHead we just loaded, but it now has a different "next" value, the tag field + // will not match, and compare_exchange_weak() will fail and prevent that particular + // race condition. + // acquire: no read/write can be reordered before this + if (mHead.compare_exchange_weak(currentHead, newHead, + std::memory_order_acquire, std::memory_order_relaxed)) { // This assert needs to occur after we have validated that there was no race condition // Otherwise, next might already contain application data, if another thread // raced ahead of us after we loaded mHead, but before we loaded mHead->next. - assert_invariant(!pNext || pNext >= pStorage); + assert(!pNext || pNext >= pStorage); break; } } void* p = (currentHead.offset >= 0) ? (pStorage + currentHead.offset) : nullptr; - assert_invariant(!p || p >= pStorage); + assert(!p || p >= pStorage); return p; } void push(void* p) noexcept { Node* const storage = mStorage; - assert_invariant(p && p >= storage); - Node* const node = static_cast(p); - HeadPtr currentHead = mHead.load(); + assert(p && p >= storage); + // we use placement-new to properly manage the lifetime + // this is noop already under O1 + Node* const node = new (p) Node; + HeadPtr currentHead = mHead.load(std::memory_order_relaxed); HeadPtr newHead = { int32_t(node - storage), currentHead.tag + 1 }; do { newHead.tag = currentHead.tag + 1; - Node* const n = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr; - node->next.store(n, std::memory_order_relaxed); - } while(!mHead.compare_exchange_weak(currentHead, newHead)); + Node* const pNext = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr; + node->next = pNext; // could be a race with pop, corrected by CAS + } while(!mHead.compare_exchange_weak(currentHead, newHead, + std::memory_order_release, std::memory_order_relaxed)); + // release: no read/write can be reordered after this } void* getFirst() noexcept { @@ -320,10 +329,7 @@ class AtomicFreeList { } struct Node { - // This should be a regular (non-atomic) pointer, but this causes TSAN to complain - // about a data-race that exists but is benin. We always use this atomic<> in - // relaxed mode. - // The data race TSAN complains about is when a pop() is interrupted by a + // There is a benign data race when a pop() is interrupted by a // pop() + push() just after mHead->next is read -- it appears as though it is written // without synchronization (by the push), however in that case, the pop's CAS will fail // and things will auto-correct. @@ -346,7 +352,7 @@ class AtomicFreeList { // | // CAS, tag++ // - std::atomic next; + Node* next = nullptr; }; private: @@ -378,9 +384,9 @@ class PoolAllocator { // our allocator concept void* alloc(size_t size = ELEMENT_SIZE, size_t alignment = ALIGNMENT, size_t offset = OFFSET) noexcept { - assert_invariant(size <= ELEMENT_SIZE); - assert_invariant(alignment <= ALIGNMENT); - assert_invariant(offset == OFFSET); + assert(size <= ELEMENT_SIZE); + assert(alignment <= ALIGNMENT); + assert(offset == OFFSET); return mFreeList.pop(); } @@ -458,7 +464,7 @@ class PoolAllocatorWithFallback : if (UTILS_UNLIKELY(!p)) { p = HeapAllocator::alloc(size, alignment); } - assert_invariant(p); + assert(p); return p; } @@ -714,13 +720,13 @@ class Arena { // trivially destructible, since free() won't call the destructor and this is allocating // an array. template ::value>::type> + typename = std::enable_if_t>> T* alloc(size_t count, size_t alignment, size_t extra) noexcept { return (T*)alloc(count * sizeof(T), alignment, extra); } template ::value>::type> + typename = std::enable_if_t>> T* alloc(size_t count, size_t alignment = alignof(T)) noexcept { return (T*)alloc(count * sizeof(T), alignment); } @@ -876,7 +882,7 @@ class ArenaScope { template T* make(ARGS&& ... args) noexcept { T* o = nullptr; - if (std::is_trivially_destructible::value) { + if (std::is_trivially_destructible_v) { o = mArena.template make(std::forward(args)...); } else { void* const p = (Finalizer*)mArena.alloc(sizeof(T), ALIGN, sizeof(Finalizer)); @@ -939,7 +945,7 @@ class STLAllocator { TYPE* allocate(std::size_t n) { auto p = static_cast(mArena.alloc(n * sizeof(TYPE), alignof(TYPE))); - assert_invariant(p); + assert(p); return p; } diff --git a/package/ios/libs/filament/include/utils/BitmaskEnum.h b/package/ios/libs/filament/include/utils/BitmaskEnum.h index 17f94d21..ae412204 100644 --- a/package/ios/libs/filament/include/utils/BitmaskEnum.h +++ b/package/ios/libs/filament/include/utils/BitmaskEnum.h @@ -41,32 +41,32 @@ size_t count(); // ------------------------------------------------------------------------------------------------ template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr int operator+(Enum value) noexcept { return int(value); } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator==(Enum lhs, size_t rhs) noexcept { using underlying_t = std::underlying_type_t; return underlying_t(lhs) == rhs; } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator==(size_t lhs, Enum rhs) noexcept { return rhs == lhs; } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator!=(Enum lhs, size_t rhs) noexcept { return !(rhs == lhs); } template::value && utils::EnableIntegerOperators::value, int> = 0> + std::is_enum_v && utils::EnableIntegerOperators::value, int> = 0> inline constexpr bool operator!=(size_t lhs, Enum rhs) noexcept { return rhs != lhs; } @@ -74,66 +74,66 @@ inline constexpr bool operator!=(size_t lhs, Enum rhs) noexcept { // ------------------------------------------------------------------------------------------------ template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr bool operator!(Enum rhs) noexcept { using underlying = std::underlying_type_t; return underlying(rhs) == 0; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator~(Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(~underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator|(Enum lhs, Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(underlying(lhs) | underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator&(Enum lhs, Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(underlying(lhs) & underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator^(Enum lhs, Enum rhs) noexcept { using underlying = std::underlying_type_t; return Enum(underlying(lhs) ^ underlying(rhs)); } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator|=(Enum& lhs, Enum rhs) noexcept { return lhs = lhs | rhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator&=(Enum& lhs, Enum rhs) noexcept { return lhs = lhs & rhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr Enum operator^=(Enum& lhs, Enum rhs) noexcept { return lhs = lhs ^ rhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr bool none(Enum lhs) noexcept { return !lhs; } template::value && utils::EnableBitMaskOperators::value, int> = 0> + std::is_enum_v && utils::EnableBitMaskOperators::value, int> = 0> inline constexpr bool any(Enum lhs) noexcept { return !none(lhs); } diff --git a/package/ios/libs/filament/include/utils/CString.h b/package/ios/libs/filament/include/utils/CString.h index d5b76951..935845f2 100644 --- a/package/ios/libs/filament/include/utils/CString.h +++ b/package/ios/libs/filament/include/utils/CString.h @@ -20,6 +20,11 @@ // NOTE: this header should not include STL headers #include +#include + +#include +#include +#include #include #include @@ -28,27 +33,22 @@ #include namespace utils { - -//! \privatesection -struct hashCStrings { - typedef const char* argument_type; - typedef size_t result_type; - result_type operator()(argument_type cstr) const noexcept { - size_t hash = 5381; - while (int const c = *cstr++) { - hash = (hash * 33u) ^ size_t(c); - } - return hash; - } -}; +namespace io { +class ostream; +} template using StringLiteral = const char[N]; - // ------------------------------------------------------------------------------------------------ class UTILS_PUBLIC CString { + static constexpr bool TRACK_AND_LOG_ALLOCATIONS = false; + + template + static constexpr bool is_char_pointer_v = + std::is_pointer_v && std::is_same_v>>; + public: using value_type = char; using size_type = uint32_t; @@ -60,7 +60,9 @@ class UTILS_PUBLIC CString { using iterator = value_type*; using const_iterator = const value_type*; - CString() noexcept {} // NOLINT(modernize-use-equals-default), Ubuntu compiler bug + CString() noexcept { + track(true); + } // Allocates memory and appends a null. This constructor can be used to hold arbitrary data // inside the string (i.e. it can contain nulls or non-ASCII encodings). @@ -71,22 +73,45 @@ class UTILS_PUBLIC CString { // inside the string. explicit CString(size_t length); + // Conversion from std::string_view + explicit CString(const std::string_view& str) + : CString(str.data(), str.size()) { + } + // Allocates memory and copies traditional C string content. Unlike the above constructor, this // does not allow embedded nulls. This is explicit because this operation is costly. - explicit CString(const char* cstr); + // This is a template to ensure it's not preferred over the string literal constructor below. + template>> + explicit CString(T cstr) : CString(cstr, cstr ? strlen(cstr) : 0) { + track(true); + } + // The string can't have NULs in it. template CString(StringLiteral const& other) noexcept // NOLINT(google-explicit-constructor) : CString(other, N - 1) { + track(true); + } + + // This constructor can be used if the string has NULs in it. + template + CString(StringLiteral const& other, size_t const length) noexcept + : CString(other, length) { + track(true); + } + + CString(StaticString const& other) noexcept // NOLINT(*-explicit-constructor) + : CString(other.c_str(), other.length()) { + track(true); } CString(const CString& rhs); CString(CString&& rhs) noexcept { + track(true); this->swap(rhs); } - CString& operator=(const CString& rhs); CString& operator=(CString&& rhs) noexcept { @@ -94,11 +119,7 @@ class UTILS_PUBLIC CString { return *this; } - ~CString() noexcept { - if (mData) { - free(mData - 1); - } - } + ~CString() noexcept; void swap(CString& other) noexcept { // don't use std::swap(), we don't want an STL dependency in this file @@ -107,8 +128,8 @@ class UTILS_PUBLIC CString { other.mCStr = temp; } - const_pointer c_str() const noexcept { return mCStr; } pointer c_str() noexcept { return mCStr; } + const_pointer c_str() const noexcept { return const_cast(this)->c_str(); } const_pointer c_str_safe() const noexcept { return mData ? c_str() : ""; } const_pointer data() const noexcept { return c_str(); } pointer data() noexcept { return c_str(); } @@ -116,32 +137,169 @@ class UTILS_PUBLIC CString { size_type length() const noexcept { return size(); } bool empty() const noexcept { return size() == 0; } - iterator begin() noexcept { return mCStr; } + iterator begin() noexcept { return c_str(); } iterator end() noexcept { return begin() + length(); } const_iterator begin() const noexcept { return data(); } const_iterator end() const noexcept { return begin() + length(); } const_iterator cbegin() const noexcept { return begin(); } const_iterator cend() const noexcept { return end(); } - CString& replace(size_type pos, size_type len, const CString& str) noexcept; - CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); } + // replace + template + CString& replace(size_type const pos, size_type const len, const StringLiteral& str) & noexcept { + return replace(pos, len, str, N - 1); + } + + CString& replace(size_type const pos, size_type const len, const CString& str) & noexcept { + return replace(pos, len, str.c_str_safe(), str.size()); + } + + template >> + CString& replace(size_type pos, size_type len, T str) & noexcept { + if (str) { + return replace(pos, len, str, strlen(str)); + } + return replace(pos, len, "", 0); + } + + template + CString&& replace(size_type pos, size_type len, const StringLiteral& str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + CString&& replace(size_type const pos, size_type const len, const CString& str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + template >> + CString&& replace(size_type pos, size_type len, T str) && noexcept { + this->replace(pos, len, str); + return std::move(*this); + } + + + // insert + CString& insert(size_type const pos, char const c) & noexcept { + const char s[1] = { c }; + return replace(pos, 0, s, 1); + } + + template + CString& insert(size_type const pos, const StringLiteral& str) & noexcept { + return replace(pos, 0, str, N - 1); + } + + CString& insert(size_type const pos, const CString& str) & noexcept { + return replace(pos, 0, str.c_str_safe(), str.size()); + } + + template >> + CString& insert(size_type pos, T str) & noexcept { + if (str) { + return replace(pos, 0, str, strlen(str)); + } + return *this; + } + + CString&& insert(size_type const pos, char const c) && noexcept { + this->insert(pos, c); + return std::move(*this); + } + + template + CString&& insert(size_type pos, const StringLiteral& str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + CString&& insert(size_type const pos, const CString& str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + template >> + CString&& insert(size_type pos, T str) && noexcept { + this->insert(pos, str); + return std::move(*this); + } + + + // append + CString& append(char const c) & noexcept { + return insert(length(), c); + } + + template + CString& append(const StringLiteral& str) & noexcept { + return insert(length(), str); + } + + CString& append(const CString& str) & noexcept { + return insert(length(), str); + } + + template>> + CString& append(T str) & noexcept { + return insert(length(), str); + } + + CString&& append(char const c) && noexcept { + this->append(c); + return std::move(*this); + } + + template + CString&& append(const StringLiteral& str) && noexcept { + this->append(str); + return std::move(*this); + } + + CString&& append(const CString& str) && noexcept { + this->append(str); + return std::move(*this); + } + + template>> + CString&& append(T str) && noexcept { + this->append(str); + return std::move(*this); + } + + // operator+= + CString& operator+=(char const c) & noexcept { + return append(c); + } + + CString& operator+=(const CString& str) & noexcept { + return append(str); + } + template + CString& operator+=(const StringLiteral& str) & noexcept { + return append(str); + } + template >> + CString& operator+=(T str) & noexcept { + return append(str); + } - const_reference operator[](size_type pos) const noexcept { + const_reference operator[](size_type const pos) const noexcept { assert(pos < size()); return begin()[pos]; } - reference operator[](size_type pos) noexcept { + reference operator[](size_type const pos) noexcept { assert(pos < size()); return begin()[pos]; } - const_reference at(size_type pos) const noexcept { + const_reference at(size_type const pos) const noexcept { assert(pos < size()); return begin()[pos]; } - reference at(size_type pos) noexcept { + reference at(size_type const pos) noexcept { assert(pos < size()); return begin()[pos]; } @@ -167,20 +325,30 @@ class UTILS_PUBLIC CString { } // placement new declared as "throw" to avoid the compiler's null-check - inline void* operator new(size_t, void* ptr) { + void* operator new(size_t, void* ptr) { assert(ptr); return ptr; } - struct Hasher : private hashCStrings { - typedef CString argument_type; - typedef size_t result_type; - result_type operator()(const argument_type& s) const noexcept { - return hashCStrings::operator()(s.c_str()); - } - }; + // conversion to std::string_view + operator std::string_view() const noexcept { + return std::string_view{data(), size()}; + } private: + static void do_tracking(bool ctor); + static void track(bool ctor) { + if constexpr (TRACK_AND_LOG_ALLOCATIONS) { + do_tracking(ctor); + } + } + + CString& replace(size_type pos, size_type len, char const* str, size_t l) & noexcept; + +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const CString& rhs); +#endif + struct Data { size_type length; }; @@ -191,17 +359,16 @@ class UTILS_PUBLIC CString { Data* mData; // Data is stored at mData[-1] }; + int compare(const std::string_view& rhs) const noexcept { + return std::string_view{data(), size()}.compare(rhs); + } + int compare(const CString& rhs) const noexcept { - size_type const lhs_size = size(); - size_type const rhs_size = rhs.size(); - if (lhs_size < rhs_size) return -1; - if (lhs_size > rhs_size) return 1; - return strncmp(data(), rhs.data(), size()); + return compare(std::string_view{rhs.data(), rhs.size()}); } friend bool operator==(CString const& lhs, CString const& rhs) noexcept { - return (lhs.data() == rhs.data()) || - ((lhs.size() == rhs.size()) && !strncmp(lhs.data(), rhs.data(), lhs.size())); + return lhs.compare(rhs) == 0; } friend bool operator!=(CString const& lhs, CString const& rhs) noexcept { return !(lhs == rhs); @@ -218,9 +385,125 @@ class UTILS_PUBLIC CString { friend bool operator<=(CString const& lhs, CString const& rhs) noexcept { return !(lhs > rhs); } + + friend bool operator==(CString const& lhs, std::string_view const& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + friend bool operator==(std::string_view const& lhs, CString const& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(CString const& lhs, std::string_view const& rhs) noexcept { + return !(lhs == rhs); + } + friend bool operator!=(std::string_view const& lhs, CString const& rhs) noexcept { + return !(lhs == rhs); + } + friend bool operator<(CString const& lhs, std::string_view const& rhs) noexcept { + return lhs.compare(rhs) < 0; + } + friend bool operator<(std::string_view const& lhs, CString const& rhs) noexcept { + return lhs.compare(rhs) < 0; + } + friend bool operator>(CString const& lhs, std::string_view const& rhs) noexcept { + return lhs.compare(rhs) > 0; + } + friend bool operator>(std::string_view const& lhs, CString const& rhs) noexcept { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(CString const& lhs, std::string_view const& rhs) noexcept { + return !(lhs < rhs); + } + friend bool operator>=(std::string_view const& lhs, CString const& rhs) noexcept { + return !(lhs < rhs); + } + friend bool operator<=(CString const& lhs, std::string_view const& rhs) noexcept { + return !(lhs > rhs); + } + friend bool operator<=(std::string_view const& lhs, CString const& rhs) noexcept { + return !(lhs > rhs); + } }; -// implement this for your type for automatic conversion to CString. Failing to do so leads +// operator+ +inline CString operator+(CString lhs, const CString& rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(CString lhs, const char* rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(const char* lhs, CString rhs) { + rhs.insert(0, lhs); + return rhs; +} + +inline CString operator+(CString lhs, char const rhs) { + lhs += rhs; + return lhs; +} + +inline CString operator+(char const lhs, CString rhs) { + rhs.insert(0, lhs); + return rhs; +} + +// CString vs StringLiteral +template + bool operator==(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} == std::string_view{rhs, N - 1}; +} +template + bool operator!=(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} != std::string_view{rhs, N - 1}; +} +template + bool operator<(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} < std::string_view{rhs, N - 1}; +} +template + bool operator>(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} > std::string_view{rhs, N - 1}; +} +template + bool operator<=(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} <= std::string_view{rhs, N - 1}; +} +template + bool operator>=(CString const& lhs, const StringLiteral& rhs) noexcept { + return std::string_view{lhs.data(), lhs.size()} >= std::string_view{rhs, N - 1}; +} + +// StringLiteral vs CString +template + bool operator==(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} == std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator!=(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} != std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator<(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} < std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator>(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} > std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator<=(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} <= std::string_view{rhs.data(), rhs.size()}; +} +template + bool operator>=(const StringLiteral& lhs, CString const& rhs) noexcept { + return std::string_view{lhs, M - 1} >= std::string_view{rhs.data(), rhs.size()}; +} + + +// Implement this for your type for automatic conversion to CString. Failing to do so leads // to a compile-time failure. template CString to_string(T value) noexcept; @@ -244,9 +527,47 @@ class UTILS_PUBLIC FixedSizeString { pointer c_str() noexcept { return mData; } private: - value_type mData[N] = {0}; + value_type mData[N] = {}; }; } // namespace utils +// heterogeneous lookup support for associative containers +namespace std { + template <> + struct hash { + using is_transparent = void; // Enable heterogeneous lookup + + size_t operator()(const utils::CString& k) const noexcept { + return compute_hash(std::string_view(k)); + } + + template && + !std::is_same_v, utils::CString>>> + size_t operator()(const T& k) const noexcept { + return compute_hash(std::string_view(k)); + } + + private: + size_t compute_hash(std::string_view k) const noexcept { + size_t hash = 5381; + for (char const c : k) { + hash = (hash * 33u) ^ size_t(c); + } + return hash; + } + }; + + template<> + struct equal_to { + using is_transparent = void; // Enable heterogeneous lookup + + template + bool operator()(const T& lhs, const U& rhs) const { + return lhs == rhs; + } + }; +} + #endif // TNT_UTILS_CSTRING_H diff --git a/package/ios/libs/filament/include/utils/CallStack.h b/package/ios/libs/filament/include/utils/CallStack.h index 33ac0b50..f8366cfa 100644 --- a/package/ios/libs/filament/include/utils/CallStack.h +++ b/package/ios/libs/filament/include/utils/CallStack.h @@ -23,9 +23,11 @@ #include #include -#include namespace utils { +namespace io { +class ostream; +} /** * CallStack captures the current's thread call stack. @@ -68,10 +70,10 @@ class CallStack { intptr_t operator [](size_t index) const; /** Demangles a C++ type name */ - static utils::CString demangleTypeName(const char* mangled); + static CString demangleTypeName(const char* mangled); template - static utils::CString typeName() { + static CString typeName() { #if UTILS_HAS_RTTI return demangleTypeName(typeid(T).name()); #else @@ -84,34 +86,34 @@ class CallStack { * This will print, when possible, the demangled names of functions corresponding to the * program-counter recorded. */ - friend utils::io::ostream& operator <<(utils::io::ostream& stream, const CallStack& callstack); + friend io::ostream& operator <<(io::ostream& stream, const CallStack& callstack); bool operator <(const CallStack& rhs) const; - inline bool operator >(const CallStack& rhs) const { + bool operator >(const CallStack& rhs) const { return rhs < *this; } - inline bool operator !=(const CallStack& rhs) const { + bool operator !=(const CallStack& rhs) const { return *this < rhs || rhs < *this; } - inline bool operator >=(const CallStack& rhs) const { + bool operator >=(const CallStack& rhs) const { return !operator <(rhs); } - inline bool operator <=(const CallStack& rhs) const { + bool operator <=(const CallStack& rhs) const { return !operator >(rhs); } - inline bool operator ==(const CallStack& rhs) const { + bool operator ==(const CallStack& rhs) const { return !operator !=(rhs); } private: void update_gcc(size_t ignore) noexcept; - static utils::CString demangle(const char* mangled); + static CString demangle(const char* mangled); static constexpr size_t NUM_FRAMES = 20; diff --git a/package/ios/libs/filament/include/utils/FixedCapacityVector.h b/package/ios/libs/filament/include/utils/FixedCapacityVector.h index 1221e7cc..fd920876 100644 --- a/package/ios/libs/filament/include/utils/FixedCapacityVector.h +++ b/package/ios/libs/filament/include/utils/FixedCapacityVector.h @@ -17,10 +17,11 @@ #ifndef TNT_UTILS_FIXEDCAPACITYVECTOR_H #define TNT_UTILS_FIXEDCAPACITYVECTOR_H +#include #include #include -#include +#include #include #include #include @@ -40,6 +41,11 @@ namespace utils { +class UTILS_PUBLIC FixedCapacityVectorBase { +protected: + UTILS_NORETURN static void capacityCheckFailed(size_t capacity, size_t size); +}; + /** * FixedCapacityVector is (almost) a drop-in replacement for std::vector<> except it has a * fixed capacity decided at runtime. The vector storage is never reallocated unless reserve() @@ -56,7 +62,7 @@ namespace utils { * the optional value argument, e.g. FixedCapacityVector(4, 0) or foo.resize(4, 0). */ template, bool CapacityCheck = true> -class UTILS_PUBLIC FixedCapacityVector { +class UTILS_PUBLIC FixedCapacityVector : protected FixedCapacityVectorBase { public: using allocator_type = A; using value_type = T; @@ -84,7 +90,7 @@ class UTILS_PUBLIC FixedCapacityVector { FixedCapacityVector() = default; explicit FixedCapacityVector(const allocator_type& allocator) noexcept - : mCapacityAllocator({}, allocator) { + : mCapacityAllocator(0, allocator) { } explicit FixedCapacityVector(size_type size, const allocator_type& allocator = allocator_type()) @@ -122,6 +128,14 @@ class UTILS_PUBLIC FixedCapacityVector { this->swap(rhs); } + FixedCapacityVector(utils::Slice rhs, + const allocator_type& alloc = allocator_type()) + : mSize(rhs.size()), + mCapacityAllocator(mSize, alloc) { + mData = this->allocator().allocate(this->capacity()); + std::uninitialized_copy(rhs.cbegin(), rhs.cend(), begin()); + } + ~FixedCapacityVector() noexcept { destroy(begin(), end()); allocator().deallocate(data(), capacity()); @@ -140,6 +154,29 @@ class UTILS_PUBLIC FixedCapacityVector { return *this; } + bool operator==(const FixedCapacityVector& rhs) const noexcept { + if (this == &rhs) { + return true; + } + if (size() != rhs.size()) { + return false; + } + return std::equal(begin(), end(), rhs.begin()); + } + + Slice as_slice() noexcept { + return { begin(), end() }; + } + + Slice as_slice() const noexcept { + return { cbegin(), cend() }; + } + + template> + inline size_t hash() const noexcept { + return as_slice().template hash(); + } + allocator_type get_allocator() const noexcept { return mCapacityAllocator.second(); } @@ -266,7 +303,7 @@ class UTILS_PUBLIC FixedCapacityVector { mSize = 0; } - void resize(size_type count) { + void resize(size_type const count) { assertCapacityForSize(count); if constexpr(std::is_trivially_constructible_v && std::is_trivially_destructible_v) { @@ -277,12 +314,12 @@ class UTILS_PUBLIC FixedCapacityVector { } } - void resize(size_type count, const_reference v) { + void resize(size_type const count, const_reference v) { assertCapacityForSize(count); resize_non_trivial(count, v); } - void swap(FixedCapacityVector& other) { + void swap(FixedCapacityVector& other) noexcept { using std::swap; swap(mData, other.mData); swap(mSize, other.mSize); @@ -326,16 +363,16 @@ class UTILS_PUBLIC FixedCapacityVector { return mCapacityAllocator.second(); } - iterator assertCapacityForSize(size_type s) { + iterator assertCapacityForSize(size_type const s) { if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) { - FILAMENT_CHECK_PRECONDITION(capacity() >= s) - << "capacity exceeded: requested size " << (unsigned long)s - << "u, available capacity " << (unsigned long)capacity() << "u."; + if (UTILS_VERY_UNLIKELY(capacity() < s)) { + capacityCheckFailed(capacity(), s); + } } return end(); } - inline void construct(iterator first, iterator last) noexcept { + void construct(iterator const first, iterator const last) noexcept { // we check for triviality here so that the implementation could be non-inline if constexpr(!std::is_trivially_constructible_v) { construct_non_trivial(first, last); @@ -358,7 +395,7 @@ class UTILS_PUBLIC FixedCapacityVector { } - inline void destroy(iterator first, iterator last) noexcept { + void destroy(iterator const first, iterator const last) noexcept { // we check for triviality here so that the implementation could be non-inline if constexpr(!std::is_trivially_destructible_v) { destroy_non_trivial(first, last); @@ -419,7 +456,7 @@ class UTILS_PUBLIC FixedCapacityVector { explicit SizeTypeWrapper(TYPE value) noexcept : value(value) { } SizeTypeWrapper& operator=(TYPE rhs) noexcept { value = rhs; return *this; } SizeTypeWrapper& operator=(SizeTypeWrapper& rhs) noexcept = delete; - operator TYPE() const noexcept { return value; } + operator TYPE() const noexcept { return value; } // NOLINT(*-explicit-constructor) }; pointer mData{}; @@ -429,4 +466,13 @@ class UTILS_PUBLIC FixedCapacityVector { } // namespace utils +namespace std { +template +struct hash> { + inline size_t operator()(utils::FixedCapacityVector const& lhs) const noexcept { + return lhs.hash(); + } +}; +} // namespace std + #endif // TNT_UTILS_FIXEDCAPACITYVECTOR_H diff --git a/package/ios/libs/filament/include/utils/Hash.h b/package/ios/libs/filament/include/utils/Hash.h new file mode 100644 index 00000000..f1ff3aff --- /dev/null +++ b/package/ios/libs/filament/include/utils/Hash.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_HASH_H +#define TNT_UTILS_HASH_H + +#include // for std::hash +#include +#include +#include + +#include +#include + +namespace utils::hash { + +// The standard CRC-32 polynomial (used in Ethernet, ZIP, PNG, etc.) +static constexpr uint32_t CRC32_POLYNOMIAL = 0xEDB88320; + +// Generates the lookup table for the CRC-32 algorithm. +inline void crc32GenerateTable(std::vector& table) { + if (table.size() != 256) { + table.resize(256); + } + + for (uint32_t i = 0; i < 256; ++i) { + uint32_t crc = i; + for (int j = 0; j < 8; ++j) { + if (crc & 1) { + crc = (crc >> 1) ^ CRC32_POLYNOMIAL; + } else { + crc = crc >> 1; + } + } + table[i] = crc; + } +} + +// Updates the CRC-32 value with a new chunk of data. This function can be called multiple times to +// process a large buffer in smaller, separate parts. +// @param previous_crc The CRC value returned from the previous call to `crc32Update`. +// @param data A pointer to the data buffer for this chunk. +// @param length The length of the data buffer in bytes. +// @return The new CRC value. +inline uint32_t crc32Update(uint32_t previous_crc, const void* data, size_t length, const std::vector& table) { + uint32_t crc = ~previous_crc; + const uint8_t* current = static_cast(data); + for (size_t i = 0; i < length; ++i) { + // The core CRC-32 calculation step. + crc = (crc >> 8) ^ table[(crc ^ current[i]) & 0xFF]; + } + return ~crc; +} + +inline size_t combine(size_t lhs, size_t rhs) noexcept { + std::pair const p{ lhs, rhs }; + return std::hash{}({ (char*)&p, sizeof(p) }); +} + +// Hash function that takes an arbitrary swath of word-aligned data. +inline uint32_t murmur3(const uint32_t* key, size_t wordCount, uint32_t seed) noexcept { + uint32_t h = seed; + size_t i = wordCount; + do { + uint32_t k = *key++; + k *= 0xcc9e2d51u; + k = (k << 15u) | (k >> 17u); + k *= 0x1b873593u; + h ^= k; + h = (h << 13u) | (h >> 19u); + h = (h * 5u) + 0xe6546b64u; + } while (--i); + h ^= wordCount; + h ^= h >> 16u; + h *= 0x85ebca6bu; + h ^= h >> 13u; + h *= 0xc2b2ae35u; + h ^= h >> 16u; + return h; +} + +// The hash yields the same result for a given byte sequence regardless of alignment. +inline uint32_t murmurSlow(const uint8_t* key, size_t byteCount, uint32_t seed) noexcept { + const size_t wordCount = (byteCount + 3) / 4; + const uint8_t* const last = key + byteCount; + + // The remainder is identical to murmur3() except an inner loop safely "reads" an entire word. + uint32_t h = seed; + size_t wc = wordCount; + do { + uint32_t k = 0; + for (int i = 0; i < 4 && key < last; ++i, ++key) { + k >>= 8; + k |= uint32_t(*key) << 24; + } + k *= 0xcc9e2d51u; + k = (k << 15u) | (k >> 17u); + k *= 0x1b873593u; + h ^= k; + h = (h << 13u) | (h >> 19u); + h = (h * 5u) + 0xe6546b64u; + } while (--wc); + h ^= wordCount; + h ^= h >> 16u; + h *= 0x85ebca6bu; + h ^= h >> 13u; + h *= 0xc2b2ae35u; + h ^= h >> 16u; + return h; +} + +template +struct MurmurHashFn { + uint32_t operator()(const T& key) const noexcept { + static_assert(0 == (sizeof(key) & 3u), "Hashing requires a size that is a multiple of 4."); + return murmur3((const uint32_t*) &key, sizeof(key) / 4, 0); + } +}; + +// combines two hashes together +template +inline void combine(size_t& seed, const T& v) noexcept { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u); +} + +// combines two hashes together, faster but less good +template +inline void combine_fast(size_t& seed, const T& v) noexcept { + std::hash hasher; + seed ^= hasher(v) << 1u; +} + +} // namespace utils::hash + +#endif // TNT_UTILS_HASH_H diff --git a/package/ios/libs/filament/include/utils/ImmutableCString.h b/package/ios/libs/filament/include/utils/ImmutableCString.h new file mode 100644 index 00000000..48fbf430 --- /dev/null +++ b/package/ios/libs/filament/include/utils/ImmutableCString.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_IMMUTABLECSTRING_H +#define TNT_UTILS_IMMUTABLECSTRING_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace utils { + + +class UTILS_PUBLIC ImmutableCString { + static constexpr bool TRACK_AND_LOG_ALLOCATIONS = false; + + template + static constexpr bool is_char_pointer_v = + std::is_pointer_v && std::is_same_v>>; + +public: + using value_type = char; + using size_type = uint32_t; + using difference_type = int32_t; + using const_reference = const value_type&; + using const_pointer = const value_type*; + using const_iterator = const value_type*; + + ImmutableCString() noexcept { + track(true, mIsStatic); + } + + // The string can't have NULs in it. + template + ImmutableCString(const char (&str)[N]) noexcept : mData(str), mSize(N - 1) { // NOLINT(*-explicit-constructor) + track(true, mIsStatic); + } + + // This constructor can be used if the string has NULs in it. + template + ImmutableCString(const char (&str)[N], size_t const length) noexcept + : mData(str), mSize(length) { + track(true, mIsStatic); + } + + template>> + explicit ImmutableCString(T cstr) { + if (cstr) { + initializeFrom(cstr, strlen(cstr)); + } + track(true, mIsStatic); + } + + ImmutableCString(const char* cstr, size_t const length) { + initializeFrom(cstr, length); + track(true, mIsStatic); + } + + ImmutableCString(StaticString const& str) // NOLINT(*-explicit-constructor) + : mData(str.data()), mSize(str.size()) { + track(true, mIsStatic); + } + + ImmutableCString(const ImmutableCString& other) { + if (other.mIsStatic) { + mIsStatic = other.mIsStatic; + mSize = other.mSize; + mData = other.mData; + } else { + initializeFrom(other.mData, other.mSize); + } + track(true, mIsStatic); + } + + ImmutableCString(ImmutableCString&& other) noexcept { + track(true, mIsStatic); + this->swap(other); + } + + ImmutableCString& operator=(const ImmutableCString& other); + + ImmutableCString& operator=(ImmutableCString&& other) noexcept; + + ~ImmutableCString() { + track(false, mIsStatic); + if (!mIsStatic) { + free(const_cast(mData)); + } + } + + bool isStatic() const noexcept { return mIsStatic; } + bool isDynamic() const noexcept { return !mIsStatic; } + + const_pointer c_str_safe() const noexcept { return mData; } + const_pointer c_str() const noexcept { return mData; } + const_pointer data() const noexcept { return mData; } + size_type size() const noexcept { return mSize; } + size_type length() const noexcept { return mSize; } + bool empty() const noexcept { return mSize == 0; } + + const_iterator begin() const noexcept { return mData; } + const_iterator end() const noexcept { return mData + mSize; } + const_iterator cbegin() const noexcept { return begin(); } + const_iterator cend() const noexcept { return end(); } + + const_reference operator[](size_type const pos) const noexcept { + assert(pos < mSize); + return mData[pos]; + } + + const_reference at(size_type const pos) const noexcept { + assert(pos < mSize); + return mData[pos]; + } + + const_reference front() const noexcept { + assert(mSize > 0); + return mData[0]; + } + + const_reference back() const noexcept { + assert(mSize > 0); + return mData[mSize - 1]; + } + + void swap(ImmutableCString& other) noexcept { + std::swap(mData, other.mData); + std::swap(mSize, other.mSize); + std::swap(mIsStatic, other.mIsStatic); + } + +private: + static void do_tracking(bool ctor, bool is_static); + static void track(bool ctor, bool is_static) { + if constexpr (TRACK_AND_LOG_ALLOCATIONS) { + do_tracking(ctor, is_static); + } + } + +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const ImmutableCString& rhs); +#endif + + void initializeFrom(const char* cstr, size_t length); + + int compare(const ImmutableCString& rhs) const noexcept { + return std::string_view{ mData, mSize }.compare({ rhs.mData, rhs.mSize }); + } + + char const* mData = ""; + uint32_t mSize = 0; + bool mIsStatic = true; + + friend bool operator==(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) != 0; + } + friend bool operator<(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) < 0; + } + friend bool operator>(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) > 0; + } + friend bool operator<=(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) <= 0; + } + friend bool operator>=(const ImmutableCString& lhs, const ImmutableCString& rhs) noexcept { + return lhs.compare(rhs) >= 0; + } +}; + +static_assert(sizeof(ImmutableCString) <= 16, "ImmutableCString should be 16 bytes or less"); + +} // namespace utils + +#endif //TNT_UTILS_IMMUTABLECSTRING_H diff --git a/package/ios/libs/filament/include/utils/InternPool.h b/package/ios/libs/filament/include/utils/InternPool.h new file mode 100644 index 00000000..b9ccccbd --- /dev/null +++ b/package/ios/libs/filament/include/utils/InternPool.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_UTILS_INTERNPOOL_H +#define TNT_UTILS_INTERNPOOL_H + +#include +#include +#include +#include +#include + +#include + +namespace utils { + +/** A reference-counted intern pool of slices of T. */ +template> +class InternPool { + struct HashSlice { + inline size_t operator()(Slice const& slice) const noexcept { + return slice.template hash(); + } + }; + + struct Entry { + uint32_t referenceCount; + FixedCapacityVector value; + }; + + using Map = tsl::robin_map, Entry, HashSlice>; + + static constexpr const char* UTILS_NONNULL MISSING_ENTRY_ERROR_STRING = + "InternPool is missing entry"; + +public: + InternPool() = default; + InternPool(InternPool const& rhs) = delete; + InternPool& operator=(InternPool const& rhs) = delete; + InternPool(InternPool&& rhs) = default; + InternPool& operator=(InternPool&& rhs) = default; + + /** Acquire an interned copy of value. */ + Slice acquire(Slice slice, size_t hash) noexcept { + if (slice.empty()) { + return { nullptr, nullptr }; + } + auto it = mMap.find(slice, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return it.key(); + } + FixedCapacityVector value(slice); + // TODO: how to use above computed hash here? + return mMap.insert({ value.as_slice(), Entry{ 1, std::move(value) } }).first.key(); + } + + inline Slice acquire(Slice slice) noexcept { + return acquire(slice, HashSlice{}(slice)); + } + + Slice acquire(FixedCapacityVector&& value, size_t hash) noexcept { + if (value.empty()) { + return { nullptr, nullptr }; + } + Slice slice = value.as_slice(); + auto it = mMap.find(slice, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return it.key(); + } + // TODO: how to use above computed hash here? + return mMap.insert({ slice, Entry{ 1, std::move(value) } }).first.key(); + } + + inline Slice acquire(FixedCapacityVector&& value) noexcept { + size_t hash = HashSlice{}(value.as_slice()); + return acquire(std::move(value), hash); + } + + inline Slice acquire(FixedCapacityVector const& value, size_t hash) noexcept { + return acquire(value.as_slice(), hash); + } + + inline Slice acquire(FixedCapacityVector const& value) noexcept { + Slice slice = value.as_slice(); + return acquire(slice, HashSlice{}(slice)); + } + + /** Release interned value. */ + void release(Slice slice, size_t hash) { + if (slice.empty()) { + return; + } + auto it = mMap.find(slice, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + // TODO: change to erase_fast + mMap.erase(it); + } + } + + inline void release(Slice slice) noexcept { + return release(slice, HashSlice{}(slice)); + } + + inline void release(FixedCapacityVector const& value, size_t hash) noexcept { + return release(value.as_slice(), hash); + } + + inline void release(FixedCapacityVector const& value) noexcept { + Slice slice = value.as_slice(); + return release(slice, HashSlice{}(slice)); + } + + /** Returns true if the pool is empty. */ + inline bool empty() const noexcept { return mMap.empty(); } + + /** Returns hash of value. */ + static size_t hash(Slice slice) noexcept { + return HashSlice{}(slice); + } + + static size_t hash(FixedCapacityVector const& value) noexcept { + return HashSlice{}(value.as_slice()); + } + +private: + Map mMap; +}; + +} // namespace utils + +#endif // TNT_UTILS_INTERNPOOL_H diff --git a/package/ios/libs/filament/include/utils/Invocable.h b/package/ios/libs/filament/include/utils/Invocable.h index 49b43071..08d42968 100644 --- a/package/ios/libs/filament/include/utils/Invocable.h +++ b/package/ios/libs/filament/include/utils/Invocable.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef TNT_UTILS_INVOKABLE_H -#define TNT_UTILS_INVOKABLE_H +#ifndef TNT_UTILS_INVOCABLE_H +#define TNT_UTILS_INVOCABLE_H #include #include @@ -23,6 +23,9 @@ #include namespace utils { +namespace io { +class ostream; +} /* * Invocable is a move-only general purpose function wrapper. Instances can @@ -48,15 +51,21 @@ template using EnableIfFnMatchesInvocable = std::enable_if_t; #endif +class InvocableBase { +protected: + static io::ostream& printInvocable(io::ostream& out, const char* name); +}; + template class Invocable; template -class Invocable { +class Invocable : protected InvocableBase { public: // Creates an Invocable that does not contain a functor. // Will evaluate to false. Invocable() = default; + Invocable(std::nullptr_t) noexcept {} ~Invocable() noexcept; @@ -69,6 +78,7 @@ class Invocable { Invocable& operator=(const Invocable&) = delete; Invocable& operator=(Invocable&& rhs) noexcept; + Invocable& operator=(std::nullptr_t) noexcept; // Invokes the invocable with the args passed in. // If the Invocable is empty, this will assert. @@ -81,6 +91,11 @@ class Invocable { explicit operator bool() const noexcept; private: +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const Invocable&) { + return printInvocable(out, "Invocable<>"); // TODO: is there a way to do better here? + } +#endif void* mInvocable = nullptr; void (*mDeleter)(void*) = nullptr; R (* mInvoker)(void*, Args...) = nullptr; @@ -128,6 +143,17 @@ Invocable& Invocable::operator=(Invocable&& rhs) noexcep return *this; } +template +Invocable& Invocable::operator=(std::nullptr_t) noexcept { + if (mDeleter) { + mDeleter(mInvocable); + } + mInvocable = nullptr; + mDeleter = nullptr; + mInvoker = nullptr; + return *this; +} + template template R Invocable::operator()(OperatorArgs&& ... args) { @@ -149,4 +175,4 @@ Invocable::operator bool() const noexcept { } // namespace utils -#endif // TNT_UTILS_INVOKABLE_H +#endif // TNT_UTILS_INVOCABLE_H diff --git a/package/ios/libs/filament/include/utils/Logger.h b/package/ios/libs/filament/include/utils/Logger.h new file mode 100644 index 00000000..b385c85b --- /dev/null +++ b/package/ios/libs/filament/include/utils/Logger.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_LOGGER_H +#define TNT_UTILS_LOGGER_H + +// Logger.h provides a subset of the Abseil logging API, offering the following macros: + +// **LOG(severity)**: Logs a message at the specified severity level. +// **DLOG(severity)**: Logs a message at the specified severity level only in debug builds. + +// Supported `severity` levels are: +// * `INFO` +// * `WARNING` +// * `ERROR` +// * `FATAL` + +// For programmatic control over logging severity, use the `LEVEL` macro: + +// **LOG(LEVEL(expression))**: Logs a message at a severity level determined by the `expression`. +// The `expression` must return a `utils::LogSeverity` value, which is equivalent to +// `absl::LogSeverity`. + +#if defined(FILAMENT_USE_ABSEIL_LOGGING) + +#include +#include + +namespace utils { +using absl::LogSeverity; +} + +#else + +#include +#include + +namespace utils { + +enum class LogSeverity : int { + kInfo = 0, + kWarning = 1, + kError = 2, + kFatal = 3, +}; + +template +class LogLine { +public: + explicit LogLine(Stream& stream) + : mStream(stream) {} + + LogLine(const LogLine&) = delete; + LogLine(LogLine&&) = delete; + LogLine& operator=(const LogLine&) = delete; + LogLine& operator=(LogLine&&) = delete; + + ~LogLine() noexcept { mStream << io::endl; } + + template + LogLine& operator<<(T&& value) { + mStream << std::forward(value); + return *this; + } + +private: + Stream& mStream; +}; + +static inline io::ostream& getLogStream(LogSeverity severity) { + switch (severity) { + case LogSeverity::kInfo: + return slog.d; + case LogSeverity::kWarning: + return slog.w; + case LogSeverity::kError: + return slog.e; + case LogSeverity::kFatal: + return slog.e; + default: + return slog.d; + } +} + +struct NoopStream final { + template + NoopStream& operator<<(const T&) { + return *this; + } +}; + +#define LOG(severity) LOG_IMPL_##severity + +#ifndef NDEBUG +#define DLOG(severity) DLOG_IMPL_##severity +#else +#define DLOG(severity) utils::NoopStream() +#endif + +#define DLOG_IMPL_INFO utils::LogLine(utils::slog.d) +#define DLOG_IMPL_WARNING utils::LogLine(utils::slog.d) +#define DLOG_IMPL_ERROR utils::LogLine(utils::slog.d) +#define DLOG_IMPL_LEVEL(severity) utils::LogLine(((void) severity, utils::slog.d)) + +#define LOG_IMPL_INFO utils::LogLine(utils::slog.i) +#define LOG_IMPL_WARNING utils::LogLine(utils::slog.w) +#define LOG_IMPL_ERROR utils::LogLine(utils::slog.e) +#define LOG_IMPL_LEVEL(severity) utils::LogLine(getLogStream(severity)) + +}// namespace utils +#endif + +#endif// TNT_UTILS_LOGGER_H diff --git a/package/ios/libs/filament/include/utils/MonotonicRingMap.h b/package/ios/libs/filament/include/utils/MonotonicRingMap.h new file mode 100644 index 00000000..01ead770 --- /dev/null +++ b/package/ios/libs/filament/include/utils/MonotonicRingMap.h @@ -0,0 +1,157 @@ +/* +* Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_MONOTONICRINGMAP_H +#define TNT_UTILS_MONOTONICRINGMAP_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace utils { + +/** + * A map-like container with a fixed capacity and monotonically increasing keys. + * When the map is full, inserting a new element overwrites the oldest one. + * This container doesn't allocate any memory on the heap. + * Lookups are O(log N). + */ +template +class MonotonicRingMap { +public: + using key_type = KEY; //!< The key type. + using mapped_type = VALUE; //!< The value type. + using value_type = std::pair; //!< The key-value pair type. + + //! Creates an empty map. + MonotonicRingMap() noexcept = default; + + //! Returns the number of elements in the map. + size_t size() const noexcept { return mSize; } + + //! Returns the maximum number of elements the map can hold. + static constexpr size_t capacity() noexcept { return N; } + + //! Returns true if the map is empty. + bool empty() const noexcept { return mSize == 0; } + + //! Returns true if the map is full. + bool full() const noexcept { return mSize == N; } + + //! Clears the map entirely. + void clear() noexcept { mSize = 0; mHead = 0; } + + /** + * Inserts a new key-value pair. + * The key must be greater than the key of the last inserted element. + * If the map is full, the oldest element is overwritten. + * @param key The key to insert. + * @param value The value to associate with the key. + */ + UTILS_NOINLINE void insert(key_type key, mapped_type value) { + assert(empty() || key > back().first); // assert monotonic + if (UTILS_LIKELY(full())) { + // container is full, replace the oldest element + mStorage[mHead] = { key, value }; + mHead = (mHead + 1) % N; + } else { + // container is not full, add to the end + const uint32_t index = (mHead + mSize) % N; + mStorage[index] = { key, value }; + mSize++; + } + } + + /** + * Finds a value by its key. + * @param key The key to look for. + * @return A pointer to the value if the key is found, nullptr otherwise. + */ + mapped_type* find(key_type key) { + return const_cast(static_cast(this)->find(key)); + } + + /** + * Finds a value by its key. + * @param key The key to look for. + * @return A pointer to the const value if the key is found, nullptr otherwise. + */ + UTILS_NOINLINE const mapped_type* find(key_type key) const { + if (empty()) { + return nullptr; + } + + if (key < front().first || key > back().first) { + return nullptr; + } + + const auto comparator = [](const value_type& element, key_type k) { + return element.first < k; + }; + + const auto endOfStorage = mStorage.cbegin() + N; + const auto headIter = mStorage.cbegin() + mHead; + + if (mHead + mSize <= N) { + // The logical sequence is contiguous in memory + const auto logicalEnd = headIter + mSize; + auto it = std::lower_bound(headIter, logicalEnd, key, comparator); + if (it != logicalEnd && it->first == key) { + return &it->second; + } + } else { // Wrapped around + // First part: mStorage[mHead...N-1] + auto it1 = std::lower_bound(headIter, endOfStorage, key, comparator); + if (it1 != endOfStorage && it1->first == key) { + return &it1->second; + } + + // Second part: mStorage[0...head-1] + const auto wrapPartEndIter = mStorage.cbegin() + ((mHead + mSize) % N); + auto it2 = std::lower_bound(mStorage.cbegin(), wrapPartEndIter, key, comparator); + if (it2 != wrapPartEndIter && it2->first == key) { + return &it2->second; + } + } + return nullptr; + } + + //! Returns a reference to the oldest element. + const value_type& front() const { + assert(!empty()); + return mStorage[mHead]; + } + + //! Returns a reference to the newest element. + const value_type& back() const { + assert(!empty()); + return mStorage[(mHead + mSize - 1) % N]; + } + +private: + std::array mStorage; + uint32_t mSize = 0; + uint32_t mHead = 0; +}; + +} // namespace utils + +#endif // TNT_UTILS_MONOTONICRINGMAP_H diff --git a/package/ios/libs/filament/include/utils/NameComponentManager.h b/package/ios/libs/filament/include/utils/NameComponentManager.h index 4ac7435a..403cc011 100644 --- a/package/ios/libs/filament/include/utils/NameComponentManager.h +++ b/package/ios/libs/filament/include/utils/NameComponentManager.h @@ -47,7 +47,7 @@ class EntityManager; * printf("%s\n", names->getName(names->getInstance(myEntity)); * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager { +class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager { public: using Instance = EntityInstance; @@ -97,7 +97,7 @@ class UTILS_PUBLIC NameComponentManager : private SingleInstanceComponentManager const char* getName(Instance instance) const noexcept; void gc(EntityManager& em) noexcept { - SingleInstanceComponentManager::gc(em, [this](Entity e) { + SingleInstanceComponentManager::gc(em, [this](Entity e) { removeComponent(e); }); } diff --git a/package/ios/libs/filament/include/utils/Panic.h b/package/ios/libs/filament/include/utils/Panic.h index 6e9ac3f4..acd19468 100644 --- a/package/ios/libs/filament/include/utils/Panic.h +++ b/package/ios/libs/filament/include/utils/Panic.h @@ -353,7 +353,7 @@ class UTILS_PUBLIC Panic { * The TPanic<> class implements the std::exception protocol as well as the Panic * interface common to all exceptions thrown by the framework. */ -template +template class UTILS_PUBLIC TPanic : public Panic { public: // std::exception protocol @@ -377,7 +377,8 @@ class UTILS_PUBLIC TPanic : public Panic { * @param file the file where the above function in implemented * @param line the line in the above file where the error was detected * @param literal a literal version of the error message - * @param format printf style string describing the error + * @param format printf style format string describing the error + * @param ... printf style arguments * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() @@ -398,12 +399,11 @@ class UTILS_PUBLIC TPanic : public Panic { * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC * @see setMode() */ - static inline void panic( + static void panic( char const* function, char const* file, int line, char const* literal, std::string reason) UTILS_NORETURN; -protected: - +private: /** * Creates a Panic with extra information about the error-site. * @param function the name of the function where the error was detected @@ -415,11 +415,14 @@ class UTILS_PUBLIC TPanic : public Panic { TPanic(char const* function, char const* file, int line, char const* literal, std::string reason); + friend class PreconditionPanic; + friend class PostconditionPanic; + friend class ArithmeticPanic; + +protected: ~TPanic() override; private: - void buildMessage(); - char const* const mFile = nullptr; // file where the panic happened char const* const mFunction = nullptr; // function where the panic happened int const mLine = -1; // line where the panic happened @@ -443,11 +446,11 @@ void panicLog( * ASSERT_PRECONDITION uses this Panic to report a precondition failure. * @see ASSERT_PRECONDITION */ -class UTILS_PUBLIC PreconditionPanic : public TPanic { +class UTILS_PUBLIC PreconditionPanic final : public TPanic { // Programming error, can be avoided // e.g.: invalid arguments - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Precondition"; }; @@ -457,11 +460,11 @@ class UTILS_PUBLIC PreconditionPanic : public TPanic { * ASSERT_POSTCONDITION uses this Panic to report a postcondition failure. * @see ASSERT_POSTCONDITION */ -class UTILS_PUBLIC PostconditionPanic : public TPanic { +class UTILS_PUBLIC PostconditionPanic final : public TPanic { // Usually only detectable at runtime - // e.g.: dead-lock would occur, arithmetic errors - using TPanic::TPanic; - friend class TPanic; + // e.g.: deadlock would occur, arithmetic errors + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Postcondition"; }; @@ -471,11 +474,11 @@ class UTILS_PUBLIC PostconditionPanic : public TPanic { * ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure. * @see ASSERT_ARITHMETIC */ -class UTILS_PUBLIC ArithmeticPanic : public TPanic { +class UTILS_PUBLIC ArithmeticPanic final : public TPanic { // A common case of post-condition error // e.g.: underflow, overflow, internal computations errors - using TPanic::TPanic; - friend class TPanic; + using TPanic::TPanic; + friend class TPanic; constexpr static auto type = "Arithmetic"; }; @@ -519,11 +522,11 @@ class UTILS_PUBLIC PanicStream { PanicStream& operator<<(const void* value) noexcept; - PanicStream& operator<<(const char* string) noexcept; - PanicStream& operator<<(const unsigned char* string) noexcept; + PanicStream& operator<<(const char* value) noexcept; + PanicStream& operator<<(const unsigned char* value) noexcept; - PanicStream& operator<<(std::string const& s) noexcept; - PanicStream& operator<<(std::string_view const& s) noexcept; + PanicStream& operator<<(std::string const& value) noexcept; + PanicStream& operator<<(std::string_view const& value) noexcept; protected: io::sstream mStream; @@ -542,6 +545,27 @@ class TPanicStream : public PanicStream { } }; +template +class FlagGuardedStream : public PanicStream { +public: + FlagGuardedStream(bool const enable, char const* function, char const* file, int const line, + char const* condition) + : PanicStream(function, file, line, condition), + mEnablePanic(enable) {} + ~FlagGuardedStream() noexcept(false) { + if (mEnablePanic) { + PanicType::panic(mFunction, mFile, mLine, mLiteral, mStream.c_str()); + } else { + logWarning(); + } + } + +private: + void logWarning() noexcept; + + bool mEnablePanic; +}; + } // namespace details // ----------------------------------------------------------------------------------------------- @@ -560,7 +584,7 @@ class TPanicStream : public PanicStream { switch (0) \ case 0: \ default: \ - UTILS_LIKELY(cond) ? (void)0 : ::utils::details::Voidify()&& + UTILS_VERY_LIKELY(cond) ? (void)0 : ::utils::details::Voidify()&& #define FILAMENT_PANIC_IMPL(message, TYPE) \ ::utils::details::TPanicStream<::utils::TYPE>(PANIC_FUNCTION, PANIC_FILE(__FILE__), __LINE__, message) @@ -580,6 +604,20 @@ class TPanicStream : public PanicStream { FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_PANIC_IMPL(#condition, ArithmeticPanic) #endif +#define FILAMENT_FLAG_GUARDED_PANIC_IMPL(flag, condition, TYPE) \ + ::utils::details::FlagGuardedStream<::utils::TYPE>(flag, PANIC_FUNCTION, PANIC_FILE(__FILE__), \ + __LINE__, #condition) + +#ifndef FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION +#define FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION(condition, flag) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_FLAG_GUARDED_PANIC_IMPL(flag, condition, PreconditionPanic) +#endif + +#ifndef FILAMENT_FLAG_GUARDED_CHECK_POSTCONDITION +#define FILAMENT_FLAG_GUARDED_CHECK_POSTCONDITION(condition, flag) \ + FILAMENT_CHECK_CONDITION_IMPL(condition) FILAMENT_FLAG_GUARDED_PANIC_IMPL(flag, condition, PostconditionPanic) +#endif + #define PANIC_PRECONDITION_IMPL(cond, format, ...) \ ::utils::PreconditionPanic::panic(PANIC_FUNCTION, \ PANIC_FILE(__FILE__), __LINE__, #cond, format, ##__VA_ARGS__) @@ -629,14 +667,14 @@ class TPanicStream : public PanicStream { * @param format printf-style string describing the error in more details */ #define ASSERT_PRECONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) + (!UTILS_VERY_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_PRECONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif @@ -657,14 +695,14 @@ class TPanicStream : public PanicStream { * @endcode */ #define ASSERT_POSTCONDITION(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) + (!UTILS_VERY_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_POSTCONDITION_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -689,10 +727,10 @@ class TPanicStream : public PanicStream { #if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG) #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_ARITHMETIC_IMPL(cond, format, ##__VA_ARGS__), false : true) #else #define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__), false : true) #endif /** @@ -717,6 +755,6 @@ class TPanicStream : public PanicStream { * @endcode */ #define ASSERT_DESTRUCTOR(cond, format, ...) \ - (!UTILS_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__) : (void)0) + (!UTILS_VERY_LIKELY(cond) ? PANIC_LOG_IMPL(cond, format, ##__VA_ARGS__) : (void)0) #endif // TNT_UTILS_PANIC_H diff --git a/package/ios/libs/filament/include/utils/RefCountedMap.h b/package/ios/libs/filament/include/utils/RefCountedMap.h new file mode 100644 index 00000000..5fc0b85f --- /dev/null +++ b/package/ios/libs/filament/include/utils/RefCountedMap.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TNT_UTILS_REFCOUNTEDMAP_H +#define TNT_UTILS_REFCOUNTEDMAP_H + +#include +#include +#include + +#include + +#include +#include +#include + +namespace utils { + +namespace refcountedmap { + +template +struct is_pointer_like_trait : std::false_type {}; + +template +struct is_pointer_like_trait())>> : std::true_type {}; + +template +inline constexpr bool IsPointer = is_pointer_like_trait::value; + +template +struct PointerTraits { + using element_type = T; +}; + +template +struct PointerTraits>> { + using element_type = typename std::pointer_traits::element_type; +}; + +template +struct DefaultValue { + T operator()() const noexcept { + return {}; + } +}; + +} // namespace refcountedmap + +/** A reference-counted map. + * + * Don't use RAII here, both because we sometimes want to deliberately leak memory, and because + * we're managing GL resources that require more managed destruction. + */ +template, + typename NullValue = refcountedmap::DefaultValue> +class RefCountedMap { + // Use references for the key if the size of the key type is greater than the size of a pointer. + using KeyRef = std::conditional_t<(sizeof(Key) > sizeof(void*)), const Key&, Key>; + using TValue = typename refcountedmap::PointerTraits::element_type; + + struct Entry { + uint32_t referenceCount; + T value; + }; + + using Map = tsl::robin_map; + + static constexpr TValue& deref(T& a) { + if constexpr (refcountedmap::IsPointer) { + return *a; + } else { + return a; + } + } + + static constexpr TValue const& deref(T const& a) { + if constexpr (refcountedmap::IsPointer) { + return *a; + } else { + return a; + } + } + + static constexpr const char* UTILS_NONNULL MISSING_ENTRY_ERROR_STRING = + "Cache is missing entry"; + static constexpr const char* UTILS_NONNULL MISSING_VALUE_ERROR_STRING = + "Attempted to get missing value"; + +public: + /** Acquire and return a value by key, initializing it with F if it doesn't exist. + * + * If F returns NullValue{}(), this indicates a failure to create the object. If T is a value + * type, the returned pointer is valid only as long as the next call to acquire() or release(). + */ + template + TValue* UTILS_NULLABLE acquire(KeyRef key, size_t hash, F factory) noexcept { + auto it = mMap.find(key, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return &deref(it.value().value); + } + T r = factory(); + if (r == NullValue{}()) { + return nullptr; + } + // TODO: how to use above computed hash here? + return &deref(mMap.insert({ key, Entry{ 1, std::move(r) } }).first.value().value); + } + + template + inline TValue* UTILS_NULLABLE acquire(KeyRef key, F factory) noexcept { + return acquire(key, Hash{}(key), std::move(factory)); + } + + /** Acquire and return a pointer to the value if one exists. + * + * It's possible to acquire a key before its value is initialized, in which case this function + * returns nullptr. + * + * If T is a value type, this pointer is valid only as long as the next call to acquire() or + * release(). + */ + TValue* UTILS_NULLABLE acquire(KeyRef key, size_t hash) noexcept { + auto it = mMap.find(key, hash); + if (it != mMap.end()) { + it.value().referenceCount++; + return &deref(it.value().value); + } + // TODO: how to use above computed hash here? + mMap.insert({ key, Entry{ 1, NullValue{}() } }); + return nullptr; + } + + inline TValue* UTILS_NULLABLE acquire(KeyRef key) noexcept { + return acquire(key, Hash{}(key)); + } + + /** Release a reference to key, destroying it with F if reference count reaches zero. + * + * Panics if no entry found in map. + */ + template + void release(KeyRef key, size_t hash, F releaser) { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + if (it.value().value != NullValue{}()){ + releaser(deref(it.value().value)); + } + // TODO: change to erase_fast + mMap.erase(it); + } + } + + template + inline void release(KeyRef key, F releaser) noexcept { + release(key, Hash{}(key), std::move(releaser)); + } + + /** Release a reference to key. + * + * Panics if no entry found in map. + */ + void release(KeyRef key, size_t hash) { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + if (--it.value().referenceCount == 0) { + // TODO: change to erase_fast + mMap.erase(it); + } + } + + inline void release(KeyRef key) noexcept { + release(key, Hash{}(key)); + } + + /** Get a value by key, initializing it with F if it doesn't exist. + * + * If F returns NullValue{}(), this indicates a failure to create the object. If T is a value + * type, the returned pointer is valid only as long as the next call to acquire() or release(). + */ + template + TValue* UTILS_NULLABLE get(KeyRef key, size_t hash, F factory) { + auto it = mMap.find(key, hash); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + const T nullValue = NullValue{}(); + if (it.value().value == nullValue) { + it.value().value = factory(); + if (it.value().value == nullValue) { + return nullptr; + } + } + return &deref(it.value().value); + } + + template + inline TValue* UTILS_NULLABLE get(KeyRef key, F factory) noexcept { + return get(key, Hash{}(key), std::move(factory)); + } + + /** Return reference to existing value by key. + * + * This reference is valid only as long as the next call to acquire() or release(). + * + * Panics if no entry found in map. + */ + TValue& get(KeyRef key, size_t hash) { + auto it = mMap.find(key); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + FILAMENT_CHECK_PRECONDITION(it.value().value != NullValue{}()) + << MISSING_VALUE_ERROR_STRING; + return deref(it.value().value); + } + + inline TValue& get(KeyRef key) noexcept { return get(key, Hash{}(key)); } + + TValue const& get(KeyRef key, size_t hash) const { + auto it = mMap.find(key); + FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING; + FILAMENT_CHECK_PRECONDITION(it.value().value != NullValue{}()) + << MISSING_VALUE_ERROR_STRING; + return deref(it->second.value); + } + + inline TValue const& get(KeyRef key) const noexcept { return get(key, Hash{}(key)); } + + /** Returns true if the map is empty. */ + inline bool empty() const noexcept { return mMap.empty(); } + +private: + Map mMap; +}; + +} + +#endif // TNT_UTILS_REFCOUNTEDMAP_H diff --git a/package/ios/libs/filament/include/utils/SingleInstanceComponentManager.h b/package/ios/libs/filament/include/utils/SingleInstanceComponentManager.h index ddd538f5..fc820ca2 100644 --- a/package/ios/libs/filament/include/utils/SingleInstanceComponentManager.h +++ b/package/ios/libs/filament/include/utils/SingleInstanceComponentManager.h @@ -108,7 +108,7 @@ class UTILS_PUBLIC SingleInstanceComponentManager { return getComponentCount() == 0; } - utils::Entity const* getEntities() const noexcept { + Entity const* getEntities() const noexcept { return data() + 1; } @@ -238,11 +238,11 @@ class UTILS_PUBLIC SingleInstanceComponentManager { default_random_engine& rng = mRng; UTILS_NOUNROLL while (count && aliveInARow < ratio) { - assert_invariant(count == getComponentCount()); + assert(count == getComponentCount()); // note: using the modulo favorizes lower number size_t const i = rng() % count; Entity const entity = pEntities[i]; - assert_invariant(entity); + assert(entity); if (UTILS_LIKELY(em.isAlive(entity))) { ++aliveInARow; continue; diff --git a/package/ios/libs/filament/include/utils/Slice.h b/package/ios/libs/filament/include/utils/Slice.h index 444a3b27..59f1043d 100644 --- a/package/ios/libs/filament/include/utils/Slice.h +++ b/package/ios/libs/filament/include/utils/Slice.h @@ -17,8 +17,11 @@ #ifndef TNT_UTILS_SLICE_H #define TNT_UTILS_SLICE_H +#include #include +#include +#include #include #include @@ -26,116 +29,126 @@ namespace utils { -/* - * A fixed-size slice of a container +/** A fixed-size slice of a container. + * + * Analogous to std::span. */ -template +template class Slice { public: + using element_type = T; + using value_type = std::remove_cv_t; + using size_type = size_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; using iterator = T*; using const_iterator = T const*; - using value_type = T; - using reference = T&; - using const_reference = T const&; - using pointer = T*; - using const_pointer = T const*; - using size_type = size_t; - Slice() noexcept = default; + Slice() = default; + Slice(iterator begin, iterator end) noexcept : mBegin(begin), mEnd(end) {} + Slice(pointer begin, size_type count) noexcept : mBegin(begin), mEnd(begin + count) {} - Slice(const_iterator begin, const_iterator end) noexcept - : mBegin(const_cast(begin)), - mEnd(const_cast(end)) { - } + Slice(Slice const& rhs) : mBegin(rhs.begin()), mEnd(rhs.end()) {} - Slice(const_pointer begin, size_type count) noexcept - : mBegin(const_cast(begin)), - mEnd(mBegin + count) { + // If Slice is Slice, define coercive copy constructor from Slice. + template + Slice(std::enable_if_t, Slice const&> rhs) + : mBegin(rhs.begin()), mEnd(rhs.end()) {} + + Slice& operator=(Slice const& rhs) noexcept { + mBegin = rhs.begin(); + mEnd = rhs.end(); + return *this; } - Slice(Slice const& rhs) noexcept = default; - Slice(Slice&& rhs) noexcept = default; - Slice& operator=(Slice const& rhs) noexcept = default; - Slice& operator=(Slice&& rhs) noexcept = default; + // If Slice is Slice, define assignment operator from Slice. + template + Slice& operator=( + std::enable_if_t, Slice const&> rhs) noexcept { + mBegin = rhs.begin(); + mEnd = rhs.end(); + return *this; + } - void set(pointer begin, size_type count) UTILS_RESTRICT noexcept { + void set(pointer begin, size_type count) noexcept { mBegin = begin; mEnd = begin + count; } - void set(iterator begin, iterator end) UTILS_RESTRICT noexcept { + void set(iterator begin, iterator end) noexcept { mBegin = begin; mEnd = end; } - void swap(Slice& rhs) UTILS_RESTRICT noexcept { + void swap(Slice& rhs) noexcept { std::swap(mBegin, rhs.mBegin); std::swap(mEnd, rhs.mEnd); } - void clear() UTILS_RESTRICT noexcept { + void clear() noexcept { mBegin = nullptr; mEnd = nullptr; } + bool operator==(Slice const& rhs) const noexcept { + if (size() != rhs.size()) { + return false; + } + if (mBegin == rhs.cbegin()) { + return true; + } + return std::equal(cbegin(), cend(), rhs.cbegin()); + } + + bool operator==(Slice const& rhs) const noexcept { + return *this == Slice(rhs); + } + // size - size_t size() const UTILS_RESTRICT noexcept { return mEnd - mBegin; } - size_t sizeInBytes() const UTILS_RESTRICT noexcept { return size() * sizeof(T); } - bool empty() const UTILS_RESTRICT noexcept { return size() == 0; } + size_t size() const noexcept { return mEnd - mBegin; } + size_t sizeInBytes() const noexcept { return size() * sizeof(T); } + bool empty() const noexcept { return size() == 0; } // iterators - iterator begin() UTILS_RESTRICT noexcept { return mBegin; } - const_iterator begin() const UTILS_RESTRICT noexcept { return mBegin; } - const_iterator cbegin() const UTILS_RESTRICT noexcept { return this->begin(); } - iterator end() UTILS_RESTRICT noexcept { return mEnd; } - const_iterator end() const UTILS_RESTRICT noexcept { return mEnd; } - const_iterator cend() const UTILS_RESTRICT noexcept { return this->end(); } + iterator begin() const noexcept { return mBegin; } + const_iterator cbegin() const noexcept { return this->begin(); } + iterator end() const noexcept { return mEnd; } + const_iterator cend() const noexcept { return this->end(); } // data access - reference operator[](size_t n) UTILS_RESTRICT noexcept { + reference operator[](size_t n) const noexcept { assert(n < size()); return mBegin[n]; } - const_reference operator[](size_t n) const UTILS_RESTRICT noexcept { - assert(n < size()); - return mBegin[n]; - } - - reference at(size_t n) UTILS_RESTRICT noexcept { + reference at(size_t n) const noexcept { return operator[](n); } - const_reference at(size_t n) const UTILS_RESTRICT noexcept { - return operator[](n); - } - - reference front() UTILS_RESTRICT noexcept { + reference front() const noexcept { assert(!empty()); return *mBegin; } - const_reference front() const UTILS_RESTRICT noexcept { - assert(!empty()); - return *mBegin; - } - - reference back() UTILS_RESTRICT noexcept { + reference back() const noexcept { assert(!empty()); return *(this->end() - 1); } - const_reference back() const UTILS_RESTRICT noexcept { - assert(!empty()); - return *(this->end() - 1); - } - - pointer data() UTILS_RESTRICT noexcept { + pointer data() const noexcept { return this->begin(); } - const_pointer data() const UTILS_RESTRICT noexcept { - return this->begin(); + template> + size_t hash() const noexcept { + Hash hasher; + size_t seed = size(); + for (auto const& it : *this) { + utils::hash::combine_fast(seed, hasher(it)); + } + return seed; } protected: @@ -145,4 +158,13 @@ class Slice { } // namespace utils +namespace std { + +template +struct hash> { + inline size_t operator()(utils::Slice const& lhs) const noexcept { return lhs.hash(); } +}; + +} // namespace std + #endif // TNT_UTILS_SLICE_H diff --git a/package/ios/libs/filament/include/utils/StaticString.h b/package/ios/libs/filament/include/utils/StaticString.h new file mode 100644 index 00000000..ae2008cb --- /dev/null +++ b/package/ios/libs/filament/include/utils/StaticString.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_STATICSTRING_H +#define TNT_UTILS_STATICSTRING_H + +#include + +#include + +#include + +namespace utils { + +class ImmutableCString; + +/** + * @brief A lightweight string class that stores a pointer to a string literal and its size, without dynamic allocation. + * + * This class is designed to efficiently represent string literals. It does not allocate any memory + * and instead relies on the compiler to manage the memory of the string literals. + */ +class UTILS_PUBLIC StaticString { +public: + using value_type = std::string_view::value_type; + using const_pointer = std::string_view::const_pointer; + using const_reference = std::string_view::const_reference; + using size_type = std::string_view::size_type; + using const_iterator = std::string_view::const_iterator; + + // Constructor from string literal + template + constexpr StaticString(const char (&str)[M]) noexcept : mString(str, M - 1) {} // NOLINT(*-explicit-constructor) + + constexpr StaticString() noexcept : mString("", 0) {} + + constexpr const_pointer c_str() const noexcept { return mString.data(); } + constexpr const_pointer data() const noexcept { return mString.data(); } + constexpr size_type size() const noexcept { return mString.size(); } + constexpr size_type length() const noexcept { return mString.size(); } + constexpr bool empty() const noexcept { return mString.empty(); } + + constexpr const_iterator begin() const noexcept { return mString.begin(); } + constexpr const_iterator end() const noexcept { return mString.end(); } + constexpr const_iterator cbegin() const noexcept { return mString.begin(); } + constexpr const_iterator cend() const noexcept { return mString.end(); } + + constexpr const_reference operator[](size_type const pos) const noexcept { + return mString[pos]; + } + + constexpr const_reference at(size_type const pos) const { + return mString[pos]; + } + + constexpr const_reference front() const noexcept { + return mString.front(); + } + + constexpr const_reference back() const noexcept { + return mString.back(); + } + + constexpr int compare(const StaticString& rhs) const noexcept { + return mString.compare(rhs.mString); + } + +private: +#if !defined(NDEBUG) + friend io::ostream& operator<<(io::ostream& out, const ImmutableCString& rhs); +#endif + + std::string_view mString; + + friend constexpr bool operator==(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString == rhs.mString; + } + + friend constexpr bool operator!=(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString != rhs.mString; + } + + friend constexpr bool operator<(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString < rhs.mString; + } + + friend constexpr bool operator>(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString > rhs.mString; + } + + friend constexpr bool operator<=(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString <= rhs.mString; + } + + friend constexpr bool operator>=(const StaticString& lhs, const StaticString& rhs) noexcept { + return lhs.mString >= rhs.mString; + } +}; + +} // namespace utils + +#endif // TNT_UTILS_STATICSTRING_H diff --git a/package/ios/libs/filament/include/utils/Status.h b/package/ios/libs/filament/include/utils/Status.h new file mode 100644 index 00000000..22a8eb4f --- /dev/null +++ b/package/ios/libs/filament/include/utils/Status.h @@ -0,0 +1,162 @@ +/* +* Copyright (C) 2025 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef TNT_UTILS_STATUS_H +#define TNT_UTILS_STATUS_H + +#include +#include +#include +#include + +namespace utils { + +/** + * A code indicating the success or failure of an operation. + */ +enum class StatusCode { + /** The operation completed successfully. */ + OK, + /** The caller provided invalid arguments in the request. */ + INVALID_ARGUMENT, + /** Internal error was occurred while processing the request. */ + INTERNAL, + /** The requested operation is not supported. */ + UNSUPPORTED, +}; + +/** + * Returns the StatusCode to indicate whether the request was successful. + * If successful, it returns OK with an optional message, if not it returns + * other codes with an optional error message. + */ +class UTILS_PUBLIC Status { +public: + /** + * Creates a new Status with a StatusCode of OK. + */ + Status() : mStatusCode(StatusCode::OK) {} + + /** + * Creates a new Status with the given status code and supplementary message. + * + * @param statusCode The status code to use. + * @param message An optional message, usually contains the reason for the failure. + */ + Status(StatusCode statusCode, std::string_view message) : + mStatusCode(statusCode), + mMessage(message.data(), message.length()) {} + + Status(const Status& other) = default; + + Status(Status&& other) noexcept = default; + + ~Status() = default; + + Status& operator=(const Status& other) = default; + Status& operator=(Status&& other) noexcept = default; + + bool operator==(const Status& other) const { + return mStatusCode == other.mStatusCode && mMessage == other.mMessage; + } + + bool operator!=(const Status& other) const { + return !(*this == other); + } + + /** + * Returns true if the status is OK. + * @return true if the operation was successful, false otherwise. + */ + bool isOk() const { + return mStatusCode == StatusCode::OK; + } + + /** + * Returns the StatusCode for this Status. + * @return the StatusCode for this Status. + */ + StatusCode getCode() const { + return mStatusCode; + } + + /** + * Returns the message for this Status. + * @return The message string. Can be empty if it's not set. + */ + std::string_view getMessage() const { + return std::string_view(mMessage.begin(), mMessage.length()); + } + + /** + * Convenient factory functions for creating Status objects. + * Example usage: `return utils::Status::internal("internal error");` + */ + + /** + * Creates a success Status with a StatusCode of OK. + * @return a success Status with a StatusCode of OK + */ + static Status ok() { + return {}; + } + + /** + * Creates a success Status with a StatusCode of OK with a supplementary message. + * @return a success Status with a StatusCode of OK with a supplementary message. + */ + static Status ok(std::string_view message) { + return {StatusCode::OK, message}; + } + + /** + * Creates an error Status with an INTERNAL status code. + * @param message The error message to include. + * @return an error Status with an INTERNAL status code. + */ + static Status internal(std::string_view message) { + return {StatusCode::INTERNAL, message}; + } + + /** + * Creates an error Status with an INVALID_ARGUMENT status code. + * @param message The error message to include. + * @return an error Status with an INVALID_ARGUMENT status code. + */ + static Status invalidArgument(std::string_view message) { + return {StatusCode::INVALID_ARGUMENT, message}; + } + + /** + * Creates an error Status with an UNSUPPORTED status code. + * @param message The error message to include. + * @return an error Status with an UNSUPPORTED status code. + */ + static Status unsupported(std::string_view message) { + return { StatusCode::UNSUPPORTED, message }; + } + +private: + StatusCode mStatusCode; + // Additional message for the Status. Usually contains the reason for the error. + utils::CString mMessage; +}; + +utils::io::ostream& operator<<(utils::io::ostream& os, const Status& status); + +} // namespace utils + +#endif // TNT_UTILS_STATUS_H diff --git a/package/ios/libs/filament/include/utils/StructureOfArrays.h b/package/ios/libs/filament/include/utils/StructureOfArrays.h index c0b2315e..923744be 100644 --- a/package/ios/libs/filament/include/utils/StructureOfArrays.h +++ b/package/ios/libs/filament/include/utils/StructureOfArrays.h @@ -20,14 +20,8 @@ #include #include #include -#include #include -#include -#include -#include -#include - #include #include // note: this is safe, see how std::array is used below (inline / private) #include @@ -35,6 +29,12 @@ #include #include +#include +#include +#include +#include +#include + namespace utils { template @@ -130,7 +130,7 @@ class StructureOfArraysBase { friend class IteratorValueRef; friend iterator; friend const_iterator; - using Type = std::tuple::type...>; + using Type = std::tuple...>; Type elements; template @@ -535,29 +535,29 @@ class StructureOfArraysBase { private: template - inline typename std::enable_if::type + inline std::enable_if_t for_each(std::tuple&, FuncT) {} template - inline typename std::enable_if::type + inline std::enable_if_t for_each(std::tuple& t, FuncT f) { f(I, std::get(t)); for_each(t, f); } template - inline typename std::enable_if::type + inline std::enable_if_t for_each_index(std::tuple&, FuncT) {} template - inline typename std::enable_if::type + inline std::enable_if_t for_each_index(std::tuple& t, FuncT f) { f.template operator()(std::get(t)); for_each_index(t, f); } inline void resizeNoCheck(size_t needed) noexcept { - assert_invariant(mCapacity >= needed); + assert(mCapacity >= needed); if (needed < mSize) { // we shrink the arrays destroy_each(needed, mSize); @@ -590,14 +590,14 @@ class StructureOfArraysBase { size_t unalignment = (offsets[i - 1] + sizes[i - 1]) % alignments[i]; size_t alignment = unalignment ? (alignments[i] - unalignment) : 0; offsets[i] = offsets[i - 1] + (sizes[i - 1] + alignment); - assert_invariant(offsets[i] % alignments[i] == 0); + assert(offsets[i] % alignments[i] == 0); } return offsets; } void construct_each(size_t from, size_t to) noexcept { forEach([from, to](auto p) { - using T = typename std::decay::type; + using T = std::decay_t; // note: scalar types like int/float get initialized to zero if constexpr (!std::is_trivially_default_constructible_v) { for (size_t i = from; i < to; i++) { @@ -609,7 +609,7 @@ class StructureOfArraysBase { void destroy_each(size_t from, size_t to) noexcept { forEach([from, to](auto p) { - using T = typename std::decay::type; + using T = std::decay_t; if constexpr (!std::is_trivially_destructible_v) { for (size_t i = from; i < to; i++) { p[i].~T(); @@ -624,7 +624,7 @@ class StructureOfArraysBase { if (mSize) { auto size = mSize; // placate a compiler warning forEach([buffer, &index, &offsets, size](auto p) { - using T = typename std::decay::type; + using T = std::decay_t; T* UTILS_RESTRICT b = static_cast(buffer); // go through each element and move them from the old array to the new @@ -671,7 +671,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::operator=( - StructureOfArraysBase::IteratorValueRef const& rhs) { + IteratorValueRef const& rhs) { return operator=(IteratorValue(rhs)); } @@ -679,7 +679,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::operator=( - StructureOfArraysBase::IteratorValueRef&& rhs) noexcept { + IteratorValueRef&& rhs) noexcept { return operator=(IteratorValue(rhs)); } @@ -688,7 +688,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::assign( - StructureOfArraysBase::IteratorValue const& rhs, std::index_sequence) { + IteratorValue const& rhs, std::index_sequence) { // implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue const& rhs) auto UTILS_UNUSED l = { (soa->elementAt(index) = std::get(rhs.elements), 0)... }; return *this; @@ -699,7 +699,7 @@ template inline typename StructureOfArraysBase::IteratorValueRef& StructureOfArraysBase::IteratorValueRef::assign( - StructureOfArraysBase::IteratorValue&& rhs, std::index_sequence) noexcept { + IteratorValue&& rhs, std::index_sequence) noexcept { // implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue&& rhs) noexcept auto UTILS_UNUSED l = { (soa->elementAt(index) = std::move(std::get(rhs.elements)), 0)... }; diff --git a/package/ios/libs/filament/include/utils/Systrace.h b/package/ios/libs/filament/include/utils/Systrace.h index 9f5a7f2a..cc99ad39 100644 --- a/package/ios/libs/filament/include/utils/Systrace.h +++ b/package/ios/libs/filament/include/utils/Systrace.h @@ -14,10 +14,16 @@ * limitations under the License. */ +/*********************************************************************** + * DEPRECATED * + * * + * Systrace.h is deprecated as a public API. There is no replacement. * + * Applications should instead use the Perfetto SDK directly. * + ***********************************************************************/ + #ifndef TNT_UTILS_SYSTRACE_H #define TNT_UTILS_SYSTRACE_H - #define SYSTRACE_TAG_NEVER (0) #define SYSTRACE_TAG_ALWAYS (1<<0) #define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles diff --git a/package/ios/libs/filament/include/utils/algorithm.h b/package/ios/libs/filament/include/utils/algorithm.h index 7a747b84..c4f400f4 100644 --- a/package/ios/libs/filament/include/utils/algorithm.h +++ b/package/ios/libs/filament/include/utils/algorithm.h @@ -39,7 +39,7 @@ constexpr inline T popcount(T v) noexcept { return (T) (v * (ONES / 255)) >> (sizeof(T) - 1) * CHAR_BIT; } -template::value>> +template>> constexpr inline T clz(T x) noexcept { static_assert(sizeof(T) * CHAR_BIT <= 128, "details::clz() only support up to 128 bits"); x |= (x >> 1u); @@ -62,7 +62,7 @@ constexpr inline T clz(T x) noexcept { return T(sizeof(T) * CHAR_BIT) - details::popcount(x); } -template::value>> +template>> constexpr inline T ctz(T x) noexcept { static_assert(sizeof(T) * CHAR_BIT <= 64, "details::ctz() only support up to 64 bits"); T c = sizeof(T) * CHAR_BIT; @@ -227,7 +227,7 @@ unsigned long long UTILS_ALWAYS_INLINE popcount(unsigned long long x) noexcept { } template::value && std::is_unsigned::value>> + typename = std::enable_if_t && std::is_unsigned_v>> constexpr inline UTILS_PUBLIC UTILS_PURE T log2i(T x) noexcept { return (sizeof(x) * 8 - 1u) - clz(x); diff --git a/package/ios/libs/filament/include/utils/bitset.h b/package/ios/libs/filament/include/utils/bitset.h index 8844fdb8..314ad2b8 100644 --- a/package/ios/libs/filament/include/utils/bitset.h +++ b/package/ios/libs/filament/include/utils/bitset.h @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -27,6 +26,7 @@ #include // for std::fill #include +#include #include #if defined(__ARM_NEON) @@ -45,8 +45,8 @@ namespace utils { */ template::value && - std::is_unsigned::value>::type> + typename = std::enable_if_t && + std::is_unsigned_v>> class UTILS_PUBLIC bitset { T storage[N]; @@ -66,12 +66,12 @@ class UTILS_PUBLIC bitset { } T getBitsAt(size_t n) const noexcept { - assert_invariant(n::max(); + } + size_t size() const noexcept { return N * BITS_PER_WORD; } bool empty() const noexcept { return none(); } @@ -104,23 +115,23 @@ class UTILS_PUBLIC bitset { bool test(size_t bit) const noexcept { return operator[](bit); } void set(size_t b) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] |= T(1) << (b % BITS_PER_WORD); } void set(size_t b, bool value) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD)); storage[b / BITS_PER_WORD] |= T(value) << (b % BITS_PER_WORD); } void unset(size_t b) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD)); } void flip(size_t b) noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); storage[b / BITS_PER_WORD] ^= T(1) << (b % BITS_PER_WORD); } @@ -133,7 +144,7 @@ class UTILS_PUBLIC bitset { } bool operator[](size_t b) const noexcept { - assert_invariant(b / BITS_PER_WORD < N); + assert(b / BITS_PER_WORD < N); return bool(storage[b / BITS_PER_WORD] & (T(1) << (b % BITS_PER_WORD))); } diff --git a/package/ios/libs/filament/include/utils/compiler.h b/package/ios/libs/filament/include/utils/compiler.h index 710c901e..79ee3dda 100644 --- a/package/ios/libs/filament/include/utils/compiler.h +++ b/package/ios/libs/filament/include/utils/compiler.h @@ -33,7 +33,7 @@ #if __has_attribute(visibility) # define UTILS_PUBLIC __attribute__((visibility("default"))) #else -# define UTILS_PUBLIC +# define UTILS_PUBLIC #endif #if __has_attribute(deprecated) @@ -104,6 +104,19 @@ # define UTILS_UNLIKELY( exp ) (!!(exp)) #endif +#if __has_builtin(__builtin_expect_with_probability) +# ifdef __cplusplus +# define UTILS_VERY_LIKELY( exp ) (__builtin_expect_with_probability( !!(exp), true, 0.995 )) +# define UTILS_VERY_UNLIKELY( exp ) (__builtin_expect_with_probability( !!(exp), false, 0.995 )) +# else +# define UTILS_VERY_LIKELY( exp ) (__builtin_expect_with_probability( !!(exp), 1, 0.995 )) +# define UTILS_VERY_UNLIKELY( exp ) (__builtin_expect_with_probability( !!(exp), 0, 0.995 )) +# endif +#else +# define UTILS_VERY_LIKELY( exp ) (!!(exp)) +# define UTILS_VERY_UNLIKELY( exp ) (!!(exp)) +#endif + #if __has_builtin(__builtin_prefetch) # define UTILS_PREFETCH( exp ) (__builtin_prefetch(exp)) #else @@ -251,7 +264,7 @@ typedef SSIZE_T ssize_t; #if defined(_MSC_VER) && !defined(__PRETTY_FUNCTION__) # define __PRETTY_FUNCTION__ __FUNCSIG__ -#endif +#endif #if defined(_MSC_VER) diff --git a/package/ios/libs/filament/include/utils/debug.h b/package/ios/libs/filament/include/utils/debug.h index 0f6ecdb2..7587c12f 100644 --- a/package/ios/libs/filament/include/utils/debug.h +++ b/package/ios/libs/filament/include/utils/debug.h @@ -28,7 +28,7 @@ void panic(const char *func, const char * file, int line, const char *assertion) # define assert_invariant(e) ((void)0) #else # define assert_invariant(e) \ - (UTILS_LIKELY(e) ? ((void)0) : utils::panic(__func__, __FILE__, __LINE__, #e)) + (UTILS_VERY_LIKELY(e) ? ((void)0) : utils::panic(__func__, __FILE__, __LINE__, #e)) #endif // NDEBUG #endif // TNT_UTILS_DEBUG_H diff --git a/package/ios/libs/filament/include/utils/memalign.h b/package/ios/libs/filament/include/utils/memalign.h index 4c048bff..a7043310 100644 --- a/package/ios/libs/filament/include/utils/memalign.h +++ b/package/ios/libs/filament/include/utils/memalign.h @@ -32,7 +32,7 @@ namespace utils { inline void* aligned_alloc(size_t size, size_t align) noexcept { // 'align' must be a power of two and a multiple of sizeof(void*) align = (align < sizeof(void*)) ? sizeof(void*) : align; - assert(align && !(align & align - 1)); + assert(align && !(align & (align - 1))); assert((align % sizeof(void*)) == 0); void* p = nullptr; @@ -40,7 +40,7 @@ inline void* aligned_alloc(size_t size, size_t align) noexcept { #if defined(WIN32) p = ::_aligned_malloc(size, align); #else - ::posix_memalign(&p, align, size); + (void) ::posix_memalign(&p, align, size); #endif return p; } @@ -73,8 +73,8 @@ class STLAlignedAllocator { using const_pointer = const TYPE*; using reference = TYPE&; using const_reference = const TYPE&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; + using size_type = ::size_t; + using difference_type = ::ptrdiff_t; using propagate_on_container_move_assignment = std::true_type; using is_always_equal = std::true_type; diff --git a/package/ios/libs/filament/include/utils/ostream.h b/package/ios/libs/filament/include/utils/ostream.h index cde8e75b..ea4952cc 100644 --- a/package/ios/libs/filament/include/utils/ostream.h +++ b/package/ios/libs/filament/include/utils/ostream.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,7 @@ namespace utils::io { struct ostream_; -class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { +class UTILS_PUBLIC ostream : protected PrivateImplementation { friend struct ostream_; public: @@ -95,11 +96,11 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { size_t length() const noexcept; private: - void reserve(size_t newSize) noexcept; + void reserve(size_t newCapacity) noexcept; char* buffer = nullptr; // buffer address char* curr = nullptr; // current pointer - size_t size = 0; // size remaining + size_t sizeRemaining = 0; // size remaining size_t capacity = 0; // total capacity of the buffer }; @@ -122,11 +123,6 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { const char* getFormat(type t) const noexcept; }; -// handles utils::bitset -inline ostream& operator << (ostream& o, utils::bitset32 const& s) noexcept { - return o << (void*)uintptr_t(s.getValue()); -} - // handles vectors from libmath (but we do this generically, without needing a dependency on libmath) template class VECTOR, typename T> inline ostream& operator<<(ostream& stream, const VECTOR& v) { @@ -144,4 +140,21 @@ inline ostream& endl(ostream& s) noexcept { return flush(s << '\n'); } } // namespace utils::io +namespace utils { + +// handles utils::bitset + +namespace io { +class ostream; +} + +inline std::ostream& operator<<(std::ostream& o, bitset32 const& s) noexcept { + return o << (void*) uintptr_t(s.getValue()); +} +inline io::ostream& operator<<(io::ostream& o, bitset32 const& s) noexcept { + return o << (void*) uintptr_t(s.getValue()); +} +}// namespace utils + + #endif // TNT_UTILS_OSTREAM_H diff --git a/package/ios/libs/filament/include/viewer/AutomationEngine.h b/package/ios/libs/filament/include/viewer/AutomationEngine.h index 8747f59d..3c7257f2 100644 --- a/package/ios/libs/filament/include/viewer/AutomationEngine.h +++ b/package/ios/libs/filament/include/viewer/AutomationEngine.h @@ -56,6 +56,22 @@ class UTILS_PUBLIC AutomationEngine { * Allows users to toggle screenshots, change the sleep duration between tests, etc. */ struct Options { + + /** + * Formats that could be used for exporting the screenshots. + */ + enum class ExportFormat : uint8_t { + /** + * Tagged Image File Format (TIFF) + */ + TIFF = 0, + + /** + * Netpbm color image format (Portable Pixel Map) + */ + PPM = 1, + }; + /** * Minimum time that automation waits between applying a settings object and advancing * to the next test case. Specified in seconds. @@ -82,6 +98,11 @@ class UTILS_PUBLIC AutomationEngine { * If true, the tick function writes out a settings JSON file before advancing. */ bool exportSettings = false; + + /** + * Which image format will be used for exporting screenshots. + */ + ExportFormat exportFormat = ExportFormat::TIFF; }; /** @@ -224,6 +245,9 @@ class UTILS_PUBLIC AutomationEngine { */ static void exportSettings(const Settings& settings, const char* filename); + static void exportScreenshot(View* view, Renderer* renderer, std::string filename, + bool autoclose, AutomationEngine* automationEngine); + Options getOptions() const { return mOptions; } bool isRunning() const { return mIsRunning; } size_t currentTest() const { return mCurrentTest; } diff --git a/package/ios/libs/filament/include/viewer/Settings.h b/package/ios/libs/filament/include/viewer/Settings.h index 2094296b..b16ee8ae 100644 --- a/package/ios/libs/filament/include/viewer/Settings.h +++ b/package/ios/libs/filament/include/viewer/Settings.h @@ -51,6 +51,7 @@ struct Settings; struct ViewSettings; struct LightSettings; struct ViewerOptions; +struct DebugOptions; enum class ToneMapping : uint8_t { LINEAR = 0, @@ -88,6 +89,8 @@ void applySettings(Engine* engine, const LightSettings& settings, IndirectLight* const utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene, View* view); void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera, Skybox* skybox, Renderer* renderer); +void applySettings(Engine* engine, const DebugOptions& settings, + Renderer* renderer); // Creates a new ColorGrading object based on the given settings. UTILS_PUBLIC @@ -246,11 +249,16 @@ struct ViewerOptions { bool autoInstancingEnabled = false; }; +struct DebugOptions { + uint16_t skipFrames = 0; +}; + struct Settings { ViewSettings view; MaterialSettings material; LightSettings lighting; ViewerOptions viewer; + DebugOptions debug; }; } // namespace viewer diff --git a/package/scripts/build-filament.sh b/package/scripts/build-filament.sh index 8a3cfef9..0c0ba51b 100755 --- a/package/scripts/build-filament.sh +++ b/package/scripts/build-filament.sh @@ -64,6 +64,11 @@ if [ "$skip_ios" = false ]; then cp -rf out/ios-$target/filament ../package/ios/libs # Rename math.h file to avoid conflicts with system header (the header doesn't seem to be used anywhere). mv ../package/ios/libs/filament/include/gltfio/math.h ../package/ios/libs/filament/include/gltfio/gltfio-math.h + + # Copy private backend headers needed for JNI initialization + echo "Copying Filament private backend headers..." + mkdir -p ../package/ios/libs/filament/include/private/backend + cp filament/backend/include/private/backend/VirtualMachineEnv.h ../package/ios/libs/filament/include/private/backend/ fi if [ "$skip_android" = false ]; then @@ -81,6 +86,11 @@ if [ "$skip_android" = false ]; then rm -rf ../package/android/libs/filament mkdir -p ../package/android/libs/filament cp -rf out/android-$target/filament ../package/android/libs + + # Copy private backend headers needed for JNI initialization + echo "Copying Filament private backend headers..." + mkdir -p ../package/android/libs/filament/include/private/backend + cp filament/backend/include/private/backend/VirtualMachineEnv.h ../package/android/libs/filament/include/private/backend/ fi echo "Done!" From 7b40174667071269a60ad9ffffc4a2a94f6395d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sat, 10 Jan 2026 17:28:04 +0100 Subject: [PATCH 7/9] fix patches for iOS simulator support --- package/filament_animator_feat.patch | 6 +- package/filament_depth_clip_mode.patch | 16 + package/filament_ios_simulator.patch | 3065 +---------------- .../libs/filament/include/gltfio/Animator.h | 2 +- .../include/gltfio/materials/uberarchive.h | 2 +- package/package.json | 2 +- 6 files changed, 95 insertions(+), 2998 deletions(-) create mode 100644 package/filament_depth_clip_mode.patch diff --git a/package/filament_animator_feat.patch b/package/filament_animator_feat.patch index 85c239f4..d362023f 100644 --- a/package/filament_animator_feat.patch +++ b/package/filament_animator_feat.patch @@ -1,5 +1,5 @@ diff --git a/libs/gltfio/include/gltfio/Animator.h b/libs/gltfio/include/gltfio/Animator.h -index 199555a40..dd550938c 100644 +index 199555a4..e02a94a6 100644 --- a/libs/gltfio/include/gltfio/Animator.h +++ b/libs/gltfio/include/gltfio/Animator.h @@ -20,6 +20,8 @@ @@ -20,7 +20,7 @@ index 199555a40..dd550938c 100644 + * This is useful if you have other instances that have the same skeleton as the animator + * from this asset, and you want those instances to be animated by the same animation (e.g. clothing). + * Usually you don't need this and using updateBoneMatrices() is enough. -+ * ++ * + * @param instance The instance to update. + */ + void updateBoneMatricesForInstance(FilamentInstance* instance); @@ -29,7 +29,7 @@ index 199555a40..dd550938c 100644 * Applies a blended transform to the union of nodes affected by two animations. * Used for cross-fading from a previous skinning-based animation or rigid body animation. diff --git a/libs/gltfio/src/Animator.cpp b/libs/gltfio/src/Animator.cpp -index 6e53b705c..ff41bdf59 100644 +index 7b1bd6eb..75db038d 100644 --- a/libs/gltfio/src/Animator.cpp +++ b/libs/gltfio/src/Animator.cpp @@ -336,6 +336,10 @@ void Animator::updateBoneMatrices() { diff --git a/package/filament_depth_clip_mode.patch b/package/filament_depth_clip_mode.patch new file mode 100644 index 00000000..776238e4 --- /dev/null +++ b/package/filament_depth_clip_mode.patch @@ -0,0 +1,16 @@ +diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm +index 1234567..abcdefg 100644 +--- a/filament/backend/src/metal/MetalDriver.mm ++++ b/filament/backend/src/metal/MetalDriver.mm +@@ -161,7 +161,10 @@ MetalDriver::MetalDriver(backend::DriverConfig const& driverConfig, + + mContext->supportsDepthClamp = false; + if (@available(macOS 10.11, iOS 11.0, *)) { +- mContext->supportsDepthClamp = true; ++ // Depth clip mode requires GPU family support (not available on older simulators) ++ mContext->supportsDepthClamp = ++ mContext->highestSupportedGpuFamily.apple >= 3 || // Apple A11 and later (iPhone 8+) ++ mContext->highestSupportedGpuFamily.mac >= 1; // All Mac GPUs + } + + // In order to support resolve store action on depth attachment, the GPU needs to support it. diff --git a/package/filament_ios_simulator.patch b/package/filament_ios_simulator.patch index 6d648759..8af6aaf7 100644 --- a/package/filament_ios_simulator.patch +++ b/package/filament_ios_simulator.patch @@ -1,8 +1,8 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 283778b32..4d4ddfc4b 100644 +index a706f439..b3043e19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -609,7 +609,11 @@ endif() +@@ -696,7 +696,11 @@ endif() string(TOLOWER "${DIST_ARCH}" DIST_ARCH) string(REPLACE "amd64" "x86_64" DIST_ARCH "${DIST_ARCH}") if (NOT DIST_DIR) @@ -15,20 +15,11 @@ index 283778b32..4d4ddfc4b 100644 endif() # ================================================================================================== -diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md -index 4a1a9c7fa..aa2ee9c76 100644 ---- a/NEW_RELEASE_NOTES.md -+++ b/NEW_RELEASE_NOTES.md -@@ -7,3 +7,4 @@ for next branch cut* header. - appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). - - ## Release notes for next branch cut -+- ios: Add support for Apple Silicon (arm64) simulator diff --git a/build.sh b/build.sh -index ab6440f45..c817f5a8d 100755 +index ecabc0fb..da9a40f2 100755 --- a/build.sh +++ b/build.sh -@@ -593,9 +593,9 @@ function build_ios_target { +@@ -679,9 +679,9 @@ function build_ios_target { local platform=$3 echo "Building iOS ${lc_target} (${arch}) for ${platform}..." @@ -40,7 +31,7 @@ index ab6440f45..c817f5a8d 100755 if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then cmake \ -@@ -611,14 +611,14 @@ function build_ios_target { +@@ -699,14 +699,14 @@ function build_ios_target { ${MATOPT_OPTION} \ ${STEREOSCOPIC_OPTION} \ ../.. @@ -57,15 +48,14 @@ index ab6440f45..c817f5a8d 100755 ${BUILD_COMMAND} ${INSTALL_COMMAND} fi -@@ -653,16 +653,26 @@ function build_ios { +@@ -741,16 +741,26 @@ function build_ios { if [[ "${ISSUE_DEBUG_BUILD}" == "true" ]]; then build_ios_target "Debug" "arm64" "iphoneos" if [[ "${IOS_BUILD_SIMULATOR}" == "true" ]]; then + build_ios_target "Debug" "arm64" "iphonesimulator" build_ios_target "Debug" "x86_64" "iphonesimulator" fi -- -+ + if [[ "${BUILD_UNIVERSAL_LIBRARIES}" == "true" ]]; then build/ios/create-universal-libs.sh \ - -o out/ios-debug/filament/lib/universal \ @@ -79,18 +69,18 @@ index ab6440f45..c817f5a8d 100755 + + rm -rf out/ios-debug/filament/lib/x86_64-iphonesimulator + rm -rf out/ios-debug/filament/lib/arm64-iphonesimulator -+ ++ + build/ios/create-xc-frameworks.sh \ + -o out/ios-debug/filament/lib \ + out/ios-debug/filament/lib/arm64-iphoneos \ -+ out/ios-debug/filament/lib/universal -+ ++ out/ios-debug/filament/lib/universal ++ + rm -rf out/ios-debug/filament/lib/universal + rm -rf out/ios-debug/filament/lib/arm64-iphoneos fi archive_ios "Debug" -@@ -670,19 +680,39 @@ function build_ios { +@@ -758,17 +768,28 @@ function build_ios { if [[ "${ISSUE_RELEASE_BUILD}" == "true" ]]; then build_ios_target "Release" "arm64" "iphoneos" @@ -98,19 +88,15 @@ index ab6440f45..c817f5a8d 100755 if [[ "${IOS_BUILD_SIMULATOR}" == "true" ]]; then + build_ios_target "Release" "arm64" "iphonesimulator" build_ios_target "Release" "x86_64" "iphonesimulator" -- fi + fi -- if [[ "${BUILD_UNIVERSAL_LIBRARIES}" == "true" ]]; then -+ # Create universal libraries - since simulator and iphone is both arm64 -+ # we can create a universal library containing both platforms + if [[ "${BUILD_UNIVERSAL_LIBRARIES}" == "true" ]]; then build/ios/create-universal-libs.sh \ - -o out/ios-release/filament/lib/universal \ - out/ios-release/filament/lib/arm64 \ - out/ios-release/filament/lib/x86_64 - rm -rf out/ios-release/filament/lib/arm64 - rm -rf out/ios-release/filament/lib/x86_64 -- fi -- + -o ./out/ios-release/filament/lib/universal \ + ./out/ios-release/filament/lib/arm64-iphonesimulator \ + ./out/ios-release/filament/lib/x86_64-iphonesimulator @@ -118,33 +104,46 @@ index ab6440f45..c817f5a8d 100755 + rm -rf out/ios-release/filament/lib/x86_64-iphonesimulator + rm -rf out/ios-release/filament/lib/arm64-iphonesimulator + -+ # Create XC Frameworks + build/ios/create-xc-frameworks.sh \ + -o out/ios-release/filament/lib \ + out/ios-release/filament/lib/arm64-iphoneos \ -+ out/ios-release/filament/lib/universal ++ out/ios-release/filament/lib/universal + + rm -rf out/ios-release/filament/lib/universal + rm -rf out/ios-release/filament/lib/arm64-iphoneos -+ else -+ # Create XC Frameworks for arm64 only - no need to create -+ # universal libraries -+ build/ios/create-xc-frameworks.sh \ -+ -o out/ios-release/filament/lib \ -+ out/ios-release/filament/lib/arm64-iphoneos -+ -+ rm -rf out/ios-release/filament/lib/arm64-iphoneos -+ fi -+ + fi + archive_ios "Release" - fi - } +diff --git a/third_party/clang/iOS.cmake b/third_party/clang/iOS.cmake +index a935c08f..ea491860 100644 +--- a/third_party/clang/iOS.cmake ++++ b/third_party/clang/iOS.cmake +@@ -29,8 +29,17 @@ SET(CMAKE_CXX_COMPILER_WORKS True) + SET(CMAKE_C_COMPILER_WORKS True) + SET(DARWIN_TARGET_OS_NAME ios) + +-SET(PLATFORM_NAME "iphoneos" CACHE STRING "iOS platform to build for") +-SET(PLATFORM_FLAG_NAME ios) ++IF(NOT DEFINED PLATFORM_NAME) ++ SET(PLATFORM_NAME "iphoneos" CACHE STRING "iOS platform to build for") ++ENDIF() ++ ++IF(PLATFORM_NAME STREQUAL "iphonesimulator") ++ SET(PLATFORM_FLAG_NAME ios-simulator) ++ MESSAGE(STATUS "Building for iOS Simulator - PLATFORM_FLAG_NAME=${PLATFORM_FLAG_NAME}") ++ELSE() ++ SET(PLATFORM_FLAG_NAME ios) ++ MESSAGE(STATUS "Building for iOS Device - PLATFORM_FLAG_NAME=${PLATFORM_FLAG_NAME}") ++ENDIF() + + IF("$ENV{RC_APPLETV}" STREQUAL "YES") + MESSAGE(STATUS "Building for tvos") diff --git a/build/ios/create-xc-frameworks.sh b/build/ios/create-xc-frameworks.sh new file mode 100755 -index 000000000..cf720cb7e +index 00000000..0314d09b --- /dev/null +++ b/build/ios/create-xc-frameworks.sh -@@ -0,0 +1,107 @@ +@@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +set -e @@ -152,32 +151,22 @@ index 000000000..cf720cb7e +function print_help { + local SELF_NAME + SELF_NAME=$(basename "$0") -+ echo "$SELF_NAME. Combine multiple single-architecture or universal libraries into xc-frameworks." ++ echo "$SELF_NAME. Create XCFrameworks from device and simulator libraries." + echo "" + echo "Usage:" -+ echo " $SELF_NAME [options] ..." ++ echo " $SELF_NAME [options] " + echo "" + echo "Options:" + echo " -h" + echo " Print this help message." + echo " -o" -+ echo " Output directory to store the xcframeworks libraries." ++ echo " Output directory to store the XCFrameworks." + echo "" + echo "Example:" -+ echo " Given the follow directories:" -+ echo " ├── universal/" -+ echo " │ └── libfoo.a <- universal library - ensure they share the same platform (iphone/simulator)" -+ echo " └── arm64-iphoneos/" -+ echo " └── libfoo.a <- arm64 iphoneos platform" -+ echo "" -+ echo " $SELF_NAME -o frameworks/ arm64-iphoneos/ universal/" -+ echo "" -+ echo " Each library is combined into an xc-framework:" -+ echo " └── frameworks/" -+ echo " └── libfoo.xcframework" ++ echo " $SELF_NAME -o out/ios-release/filament/lib \\" ++ echo " out/ios-release/filament/lib/arm64-iphoneos \\" ++ echo " out/ios-release/filament/lib/universal" + echo "" -+ echo "Each should contain one or more single or universal-architecture static libraries." -+ echo "All s should contain the same number of libraries, with the same names." +} + +OUTPUT_DIR="" @@ -199,10 +188,11 @@ index 000000000..cf720cb7e + +shift $((OPTIND - 1)) + -+PATHS=("$@") ++DEVICE_PATH="$1" ++SIMULATOR_PATH="$2" + -+if [[ ! "${PATHS[*]}" ]]; then -+ echo "One or more paths required." ++if [[ ! "${DEVICE_PATH}" ]] || [[ ! "${SIMULATOR_PATH}" ]]; then ++ echo "Both device and simulator paths required." + print_help + exit 1 +fi @@ -216,2942 +206,33 @@ index 000000000..cf720cb7e +# Create the output directory, if it doesn't exist already. +mkdir -p "${OUTPUT_DIR}" + -+# Use the first path as the "leader" path. All paths should contain the same number of files with -+# the same names, so it doesn't matter which we chose. -+LEADER_PATH="${PATHS[0]}" -+ -+echo "Creating XC-Frameworks from path: ${LEADER_PATH}..." -+ -+# Loop through each file in the leader path. For each library we find, we'll collect additional -+# architectures in the other paths and combine them all into a universal library. -+for FILE in "${LEADER_PATH}"/*.a; do ++# Loop through each library in the device path and create an XCFramework ++for FILE in "${DEVICE_PATH}"/*.a; do + [ -f "${FILE}" ] || continue + + # The static library file name, like "libfilament.a" + LIBRARY_NAME="${FILE##*/}" + -+ INPUT_FILES=("-library ${LEADER_PATH}/${LIBRARY_NAME}") -+ for ARCH_PATH in "${PATHS[@]:1}"; do -+ THIS_FILE="${ARCH_PATH}/${LIBRARY_NAME}" -+ if [[ -f "${THIS_FILE}" ]]; then -+ INPUT_FILES+=("-library ${THIS_FILE}") -+ else -+ echo "Error: ${THIS_FILE} does not exist." -+ exit 1 -+ fi -+ done ++ # Framework name without extension, like "libfilament" ++ FRAMEWORK_NAME="${LIBRARY_NAME%.a}" + -+ # Remove the .a extension -+ LIBRARY_NAME="${LIBRARY_NAME%.a}" -+ -+ OUTPUT="${OUTPUT_DIR}/${LIBRARY_NAME}.xcframework" -+ # Delete previous xcframework -+ rm -rf $OUTPUT ++ echo "Creating XCFramework for library: ${LIBRARY_NAME}" + -+ # Create the xcframework command and execute it -+ CMD="xcodebuild -create-xcframework ${INPUT_FILES[@]} -output ${OUTPUT}" -+ eval $CMD -+done -diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec -index 45fce3980..b9674ca59 100644 ---- a/ios/CocoaPods/Filament.podspec -+++ b/ios/CocoaPods/Filament.podspec -@@ -8,12 +8,6 @@ Pod::Spec.new do |spec| - spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.0/filament-v1.53.0-ios.tgz" } - -- # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. -- spec.pod_target_xcconfig = { -- 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' -- } -- spec.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } -- - spec.subspec "filament" do |ss| - ss.source_files = - "include/filament/*.h", -@@ -23,13 +17,13 @@ Pod::Spec.new do |spec| - "include/ibl/*.h", - "include/geometry/*.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = -- "lib/universal/libfilament.a", -- "lib/universal/libbackend.a", -- "lib/universal/libfilabridge.a", -- "lib/universal/libfilaflat.a", -- "lib/universal/libibl.a", -- "lib/universal/libgeometry.a" -+ ss.vendored_frameworks = -+ "lib/libfilament.xcframework", -+ "lib/libbackend.xcframework", -+ "lib/libfilabridge.xcframework", -+ "lib/libfilaflat.xcframework", -+ "lib/libibl.xcframework", -+ "lib/libgeometry.xcframework" - ss.dependency "Filament/utils" - ss.dependency "Filament/math" - end -@@ -40,11 +34,11 @@ Pod::Spec.new do |spec| - "include/filament/MaterialChunkType.h", - "include/filament/MaterialEnums.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = -- "lib/universal/libfilamat.a", -- "lib/universal/libshaders.a", -- "lib/universal/libsmol-v.a", -- "lib/universal/libfilabridge.a" -+ ss.vendored_frameworks = -+ "lib/libfilamat.xcframework", -+ "lib/libshaders.xcframework", -+ "lib/libsmol-v.xcframework", -+ "lib/libfilabridge.xcframework" - ss.dependency "Filament/utils" - ss.dependency "Filament/math" - end -@@ -52,11 +46,11 @@ Pod::Spec.new do |spec| - spec.subspec "gltfio_core" do |ss| - ss.source_files = "include/gltfio/**/*.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = -- "lib/universal/libgltfio_core.a", -- "lib/universal/libdracodec.a", -- "lib/universal/libuberarchive.a", -- "lib/universal/libstb.a" -+ ss.vendored_frameworks = -+ "lib/libgltfio_core.xcframework", -+ "lib/libdracodec.xcframework", -+ "lib/libuberarchive.xcframework", -+ "lib/libstb.xcframework" - ss.dependency "Filament/filament" - ss.dependency "Filament/ktxreader" - ss.dependency "Filament/uberz" -@@ -64,23 +58,23 @@ Pod::Spec.new do |spec| - - spec.subspec "camutils" do |ss| - ss.source_files = "include/camutils/*.h" -- ss.vendored_libraries = "lib/universal/libcamutils.a" -+ ss.vendored_frameworks = "lib/libcamutils.xcframework" - ss.header_dir = "camutils" - ss.dependency "Filament/math" - end - - spec.subspec "filameshio" do |ss| - ss.source_files = "include/filameshio/*.h" -- ss.vendored_libraries = -- "lib/universal/libfilameshio.a", -- "lib/universal/libmeshoptimizer.a" -+ ss.vendored_frameworks = -+ "lib/libfilameshio.xcframework", -+ "lib/libmeshoptimizer.xcframework" - ss.header_dir = "filameshio" - ss.dependency "Filament/filament" - end - - spec.subspec "image" do |ss| - ss.source_files = "include/image/*.h" -- ss.vendored_libraries = "lib/universal/libimage.a" -+ ss.vendored_frameworks = "lib/libimage.xcframework" - ss.header_dir = "image" - ss.dependency "Filament/filament" - end -@@ -88,7 +82,7 @@ Pod::Spec.new do |spec| - spec.subspec "utils" do |ss| - ss.source_files = "include/utils/**/*.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = "lib/universal/libutils.a" -+ ss.vendored_frameworks = "lib/libutils.xcframework" - ss.dependency "Filament/tsl" - end - -@@ -105,9 +99,9 @@ Pod::Spec.new do |spec| - spec.subspec "ktxreader" do |ss| - ss.source_files = "include/ktxreader/*.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = -- "lib/universal/libktxreader.a", -- "lib/universal/libbasis_transcoder.a" -+ ss.vendored_frameworks = -+ "lib/libktxreader.xcframework", -+ "lib/libbasis_transcoder.xcframework" - ss.dependency "Filament/image" - ss.dependency "Filament/filament" - end -@@ -115,9 +109,9 @@ Pod::Spec.new do |spec| - spec.subspec "viewer" do |ss| - ss.source_files = "include/viewer/*.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = -- "lib/universal/libviewer.a", -- "lib/universal/libcivetweb.a" -+ ss.vendored_frameworks = -+ "lib/libviewer.xcframework", -+ "lib/libcivetweb.xcframework" - ss.dependency "Filament/filament" - ss.dependency "Filament/gltfio_core" - end -@@ -125,9 +119,9 @@ Pod::Spec.new do |spec| - spec.subspec "uberz" do |ss| - ss.source_files = "include/uberz/*.h" - ss.header_mappings_dir = "include" -- ss.vendored_libraries = -- "lib/universal/libuberzlib.a", -- "lib/universal/libzstd.a" -+ ss.vendored_frameworks = -+ "lib/libuberzlib.xcframework", -+ "lib/libzstd.xcframework" - ss.header_dir = "uberz" - ss.dependency "Filament/filamat" - ss.dependency "Filament/tsl" -diff --git a/ios/samples/README.md b/ios/samples/README.md -index 4c132849e..6c9c165ea 100644 ---- a/ios/samples/README.md -+++ b/ios/samples/README.md -@@ -47,7 +47,7 @@ build Filament in Release mode, replace `debug` with `release` in the above `bui - - If you also want to be able to run on the iOS simulator, add the `-s` flag to the `build.sh` - command. For example, the following command will build for both devices (ARM64) and the simulator --(x86_64) in Debug mode: -+(x86_64/ARM64) in Debug mode: - - ``` - $ ./build.sh -s -p ios -i debug -diff --git a/ios/samples/app-template.yml b/ios/samples/app-template.yml -index e8e14c507..9c407448d 100644 ---- a/ios/samples/app-template.yml -+++ b/ios/samples/app-template.yml -@@ -17,25 +17,27 @@ targetTemplates: - configVariants: - - Metal - - OpenGL -+ dependencies: -+ - framework: "../../out/ios-release/filament/lib/libbackend.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libfilament.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libfilaflat.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libktxreader.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libfilabridge.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libutils.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libsmol-v.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libgeometry.xcframework" -+ - framework: "../../out/ios-release/filament/lib/libibl.xcframework" - settings: - base: -- OTHER_LDFLAGS: ["-lfilament", "-lbackend", "-lfilaflat", "-lktxreader", -- "-lfilabridge", "-lutils", "-lsmol-v", "-lgeometry", "-libl"] - ENABLE_BITCODE: NO - CLANG_CXX_LANGUAGE_STANDARD: gnu++17 - # This allows users to not have to specify a unique bundle ID when building the sample apps. - SAMPLE_CODE_DISAMBIGUATOR: ${DEVELOPMENT_TEAM} - configs: - debug: -- HEADER_SEARCH_PATHS: ["../../../out/ios-debug/filament/include", "generated"] -- LIBRARY_SEARCH_PATHS: -- - "../../../out/ios-debug/filament/lib/$(CURRENT_ARCH)" -- - "../../../out/ios-debug/filament/lib/universal" -+ HEADER_SEARCH_PATHS: ["../../../out/ios-debug/filament/include", "generated"] - release: -- HEADER_SEARCH_PATHS: ["../../../out/ios-release/filament/include", "generated"] -- LIBRARY_SEARCH_PATHS: -- - "../../../out/ios-release/filament/lib/$(CURRENT_ARCH)" -- - "../../../out/ios-release/filament/lib/universal" -+ HEADER_SEARCH_PATHS: ["../../../out/ios-release/filament/include", "generated"] - metal: - GCC_PREPROCESSOR_DEFINITIONS: ["FILAMENT_APP_USE_METAL=1", "$(inherited)"] - opengl: -diff --git a/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj b/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj -index b47f4c00e..ea4c11fe0 100644 ---- a/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj -+++ b/ios/samples/backend-test/backend-test.xcodeproj/project.pbxproj -@@ -3,34 +3,112 @@ - archiveVersion = 1; - classes = { - }; -- objectVersion = 51; -+ objectVersion = 54; - objects = { - - /* Begin PBXBuildFile section */ -+ 0DE5DD77BF1B9049D63B76D0 /* libutils.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7C969D515221A7739C68F3AC /* libutils.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 20863805FC626BF952BA3275 /* ViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33D80A4472074C601C57C773 /* ViewController.mm */; }; -+ 2AABB2C1262EBA9553666078 /* libsmol-v.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8F108854ADC54BBAA29C2055 /* libsmol-v.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ 32AC9F2183DD11F130D4A91D /* libfilament.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 912652335565C18B252345DE /* libfilament.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 376F63E72973F07BF618082D /* FilamentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = B54DFEB4DB6ACC0289FA3046 /* FilamentView.mm */; }; -+ 3D73E90782CC3A2124DF046F /* libfilaflat.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E8375B927AFDFD1069016D1 /* libfilaflat.xcframework */; }; - 477A2A67D32B7A3AF2E7B2F4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EB0A767D257F3D05BE9F0960 /* LaunchScreen.storyboard */; }; -+ 66014C413BCBF00E8E6DAF76 /* libgeometry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB3F32D8D3D7310FA3A0C1A1 /* libgeometry.xcframework */; }; -+ 6A5D5E984CA862B943742745 /* libgeometry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB3F32D8D3D7310FA3A0C1A1 /* libgeometry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ 7D8D3494388D7BF4D9FE4FCE /* libfilamat.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4BDDF85AADA2069C0F9FBAE /* libfilamat.xcframework */; }; -+ 7E671E777FDB5F34AAA13D5A /* libutils.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C969D515221A7739C68F3AC /* libutils.xcframework */; }; -+ 8095A6D37F6F1C9C13BBFEA8 /* libfilamat.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C4BDDF85AADA2069C0F9FBAE /* libfilamat.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ 8402B2AC6D081D1E8E065552 /* libbackend.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8632D4625CFCBD32A46772B5 /* libbackend.xcframework */; }; -+ 88C039F10343B8598FD5FF7F /* libfilabridge.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2A3753419FC4A7D4C039868 /* libfilabridge.xcframework */; }; - 91CCBA3BA96DC289D0E8E975 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE6C78F32A0494087B0CEF32 /* main.mm */; }; -+ AA5AB498C5A6117057B06824 /* libfilaflat.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9E8375B927AFDFD1069016D1 /* libfilaflat.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ AE63AFEA7E7D507BB677C158 /* libbackend.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8632D4625CFCBD32A46772B5 /* libbackend.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B3684DB18E82C867020294F1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDB89AD408A508D6CB74C32 /* AppDelegate.m */; }; -+ B73B15843FCEEEB637C636F4 /* libbackend_test.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 01B66093324E8D661CBEBB0F /* libbackend_test.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ BFE92829375EE1DBA1E4A689 /* libfilabridge.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B2A3753419FC4A7D4C039868 /* libfilabridge.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C74DDEF71B6F3C060BB56189 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B63A6E45E40F58037ABD8F84 /* Main.storyboard */; }; -+ C8C8BF3416B9B37003F1031E /* libktxreader.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 19CBBB6C426E8D038BDAC97B /* libktxreader.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ D0E54C592A378E549C96A54D /* libibl.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D1E30700B368B5F84196FD97 /* libibl.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; -+ D472EB54D462394FE99DCE2B /* libibl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1E30700B368B5F84196FD97 /* libibl.xcframework */; }; -+ D5B2F0A85F7C6F352AA99DF1 /* libsmol-v.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F108854ADC54BBAA29C2055 /* libsmol-v.xcframework */; }; -+ F571BE7CBFBADBDF42C7C23F /* libbackend_test.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01B66093324E8D661CBEBB0F /* libbackend_test.xcframework */; }; -+ F5E844BFD77174F770543A87 /* libfilament.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 912652335565C18B252345DE /* libfilament.xcframework */; }; -+ FB393C813D7A30404A2DEF23 /* libktxreader.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 19CBBB6C426E8D038BDAC97B /* libktxreader.xcframework */; }; - FC9D623AC5CF31BA9FB0C47B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93394D7D4A78C56A2BD31A7D /* Assets.xcassets */; }; - /* End PBXBuildFile section */ - -+/* Begin PBXCopyFilesBuildPhase section */ -+ 0ABB76F2D4011A4AF5186FCA /* Embed Frameworks */ = { -+ isa = PBXCopyFilesBuildPhase; -+ buildActionMask = 2147483647; -+ dstPath = ""; -+ dstSubfolderSpec = 10; -+ files = ( -+ AE63AFEA7E7D507BB677C158 /* libbackend.xcframework in Embed Frameworks */, -+ 32AC9F2183DD11F130D4A91D /* libfilament.xcframework in Embed Frameworks */, -+ AA5AB498C5A6117057B06824 /* libfilaflat.xcframework in Embed Frameworks */, -+ C8C8BF3416B9B37003F1031E /* libktxreader.xcframework in Embed Frameworks */, -+ BFE92829375EE1DBA1E4A689 /* libfilabridge.xcframework in Embed Frameworks */, -+ 0DE5DD77BF1B9049D63B76D0 /* libutils.xcframework in Embed Frameworks */, -+ 2AABB2C1262EBA9553666078 /* libsmol-v.xcframework in Embed Frameworks */, -+ 6A5D5E984CA862B943742745 /* libgeometry.xcframework in Embed Frameworks */, -+ D0E54C592A378E549C96A54D /* libibl.xcframework in Embed Frameworks */, -+ B73B15843FCEEEB637C636F4 /* libbackend_test.xcframework in Embed Frameworks */, -+ 8095A6D37F6F1C9C13BBFEA8 /* libfilamat.xcframework in Embed Frameworks */, -+ ); -+ name = "Embed Frameworks"; -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXCopyFilesBuildPhase section */ ++ SIMULATOR_FILE="${SIMULATOR_PATH}/${LIBRARY_NAME}" ++ if [[ ! -f "${SIMULATOR_FILE}" ]]; then ++ echo "Warning: ${SIMULATOR_FILE} does not exist. Skipping." ++ continue ++ fi + - /* Begin PBXFileReference section */ -+ 01B66093324E8D661CBEBB0F /* libbackend_test.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libbackend_test.xcframework; path = "$(SRCROOT)/../../../out/ios-release/filament/lib/libbackend_test.xcframework"; sourceTree = ""; }; - 056C16E7805E8D33D4C8F7B4 /* FilamentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FilamentView.h; sourceTree = ""; }; - 13DDE7723EC1A6EFCA4D2D58 /* backend-test.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "backend-test.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 17364ECD407006EEBDC06024 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; -+ 19CBBB6C426E8D038BDAC97B /* libktxreader.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libktxreader.xcframework; path = "../../../out/ios-release/filament/lib/libktxreader.xcframework"; sourceTree = ""; }; - 276A0ADD5B521719BBB1DA21 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 33D80A4472074C601C57C773 /* ViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewController.mm; sourceTree = ""; }; - 5FDB89AD408A508D6CB74C32 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 603475CEE63507AA04A9B21A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; -+ 7C969D515221A7739C68F3AC /* libutils.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libutils.xcframework; path = "../../../out/ios-release/filament/lib/libutils.xcframework"; sourceTree = ""; }; -+ 8632D4625CFCBD32A46772B5 /* libbackend.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libbackend.xcframework; path = "../../../out/ios-release/filament/lib/libbackend.xcframework"; sourceTree = ""; }; -+ 8F108854ADC54BBAA29C2055 /* libsmol-v.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = "libsmol-v.xcframework"; path = "../../../out/ios-release/filament/lib/libsmol-v.xcframework"; sourceTree = ""; }; -+ 912652335565C18B252345DE /* libfilament.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libfilament.xcframework; path = "../../../out/ios-release/filament/lib/libfilament.xcframework"; sourceTree = ""; }; - 93394D7D4A78C56A2BD31A7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; -+ 9E8375B927AFDFD1069016D1 /* libfilaflat.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libfilaflat.xcframework; path = "../../../out/ios-release/filament/lib/libfilaflat.xcframework"; sourceTree = ""; }; - A283B2D6DB365368855E2D7A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - AE6C78F32A0494087B0CEF32 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; -+ B2A3753419FC4A7D4C039868 /* libfilabridge.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libfilabridge.xcframework; path = "../../../out/ios-release/filament/lib/libfilabridge.xcframework"; sourceTree = ""; }; - B54DFEB4DB6ACC0289FA3046 /* FilamentView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilamentView.mm; sourceTree = ""; }; -+ C4BDDF85AADA2069C0F9FBAE /* libfilamat.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libfilamat.xcframework; path = "$(SRCROOT)/../../../out/ios-release/filament/lib/libfilamat.xcframework"; sourceTree = ""; }; - C76230D48038E8109EC121B0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; -+ D1E30700B368B5F84196FD97 /* libibl.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libibl.xcframework; path = "../../../out/ios-release/filament/lib/libibl.xcframework"; sourceTree = ""; }; -+ DB3F32D8D3D7310FA3A0C1A1 /* libgeometry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libgeometry.xcframework; path = "../../../out/ios-release/filament/lib/libgeometry.xcframework"; sourceTree = ""; }; - /* End PBXFileReference section */ - -+/* Begin PBXFrameworksBuildPhase section */ -+ 5E31B50ED1338E6ADE1FA49A /* Frameworks */ = { -+ isa = PBXFrameworksBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ 8402B2AC6D081D1E8E065552 /* libbackend.xcframework in Frameworks */, -+ F5E844BFD77174F770543A87 /* libfilament.xcframework in Frameworks */, -+ 3D73E90782CC3A2124DF046F /* libfilaflat.xcframework in Frameworks */, -+ FB393C813D7A30404A2DEF23 /* libktxreader.xcframework in Frameworks */, -+ 88C039F10343B8598FD5FF7F /* libfilabridge.xcframework in Frameworks */, -+ 7E671E777FDB5F34AAA13D5A /* libutils.xcframework in Frameworks */, -+ D5B2F0A85F7C6F352AA99DF1 /* libsmol-v.xcframework in Frameworks */, -+ 66014C413BCBF00E8E6DAF76 /* libgeometry.xcframework in Frameworks */, -+ D472EB54D462394FE99DCE2B /* libibl.xcframework in Frameworks */, -+ F571BE7CBFBADBDF42C7C23F /* libbackend_test.xcframework in Frameworks */, -+ 7D8D3494388D7BF4D9FE4FCE /* libfilamat.xcframework in Frameworks */, -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ }; -+/* End PBXFrameworksBuildPhase section */ -+ - /* Begin PBXGroup section */ - 05CA69F10D830E40A8FB561D /* Products */ = { - isa = PBXGroup; -@@ -44,10 +122,29 @@ - isa = PBXGroup; - children = ( - FE198F0E1EF047534F8F3128 /* backend-test */, -+ 81BF22C13654E58F622E6BEC /* Frameworks */, - 05CA69F10D830E40A8FB561D /* Products */, - ); - sourceTree = ""; - }; -+ 81BF22C13654E58F622E6BEC /* Frameworks */ = { -+ isa = PBXGroup; -+ children = ( -+ 01B66093324E8D661CBEBB0F /* libbackend_test.xcframework */, -+ 8632D4625CFCBD32A46772B5 /* libbackend.xcframework */, -+ B2A3753419FC4A7D4C039868 /* libfilabridge.xcframework */, -+ 9E8375B927AFDFD1069016D1 /* libfilaflat.xcframework */, -+ C4BDDF85AADA2069C0F9FBAE /* libfilamat.xcframework */, -+ 912652335565C18B252345DE /* libfilament.xcframework */, -+ DB3F32D8D3D7310FA3A0C1A1 /* libgeometry.xcframework */, -+ D1E30700B368B5F84196FD97 /* libibl.xcframework */, -+ 19CBBB6C426E8D038BDAC97B /* libktxreader.xcframework */, -+ 8F108854ADC54BBAA29C2055 /* libsmol-v.xcframework */, -+ 7C969D515221A7739C68F3AC /* libutils.xcframework */, -+ ); -+ name = Frameworks; -+ sourceTree = ""; -+ }; - FE198F0E1EF047534F8F3128 /* backend-test */ = { - isa = PBXGroup; - children = ( -@@ -75,6 +172,8 @@ - buildPhases = ( - 445863ED54C294354A388281 /* Sources */, - BD71575ED7ED3237FEFD94C7 /* Resources */, -+ 5E31B50ED1338E6ADE1FA49A /* Frameworks */, -+ 0ABB76F2D4011A4AF5186FCA /* Embed Frameworks */, - ); - buildRules = ( - ); -@@ -91,12 +190,13 @@ - 8A7A83D59A7827D3335343C2 /* Project object */ = { - isa = PBXProject; - attributes = { -- LastUpgradeCheck = 1200; -+ BuildIndependentTargetsInParallel = YES; -+ LastUpgradeCheck = 1430; - TargetAttributes = { - }; - }; - buildConfigurationList = E89E1EEA671E49DBAC9D5A9C /* Build configuration list for PBXProject "backend-test" */; -- compatibilityVersion = "Xcode 11.0"; -+ compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( -@@ -221,6 +321,11 @@ - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; -+ FRAMEWORK_SEARCH_PATHS = ( -+ "$(inherited)", -+ "\"$(SRCROOT)/../../../out/ios-release/filament/lib\"", -+ "\"../../../out/ios-release/filament/lib\"", -+ ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "FILAMENT_APP_USE_OPENGL=1", - "$(inherited)", -@@ -235,22 +340,8 @@ - "$(inherited)", - "@executable_path/Frameworks", - ); -- LIBRARY_SEARCH_PATHS = ( -- "../../../out/ios-debug/filament/lib/$(CURRENT_ARCH)", -- "../../../out/ios-debug/filament/lib/universal", -- ); - OTHER_LDFLAGS = ( -- "-lfilament", -- "-lbackend", -- "-lfilaflat", -- "-lktxreader", -- "-lfilabridge", -- "-lutils", -- "-lsmol-v", -- "-lgeometry", -- "-libl", -- "-lfilamat", -- "-force_load ../../../out/ios-debug/filament/lib/arm64/libbackend_test.a", -+ "-force_load ../../../out/ios-release/filament/lib/arm64/libbackend_test.xcframework", - ); - PRODUCT_BUNDLE_IDENTIFIER = "${SAMPLE_CODE_DISAMBIGUATOR}.google.filament.backend-test"; - SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; -@@ -328,6 +419,11 @@ - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; -+ FRAMEWORK_SEARCH_PATHS = ( -+ "$(inherited)", -+ "\"$(SRCROOT)/../../../out/ios-release/filament/lib\"", -+ "\"../../../out/ios-release/filament/lib\"", -+ ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "FILAMENT_APP_USE_OPENGL=1", - "$(inherited)", -@@ -342,22 +438,8 @@ - "$(inherited)", - "@executable_path/Frameworks", - ); -- LIBRARY_SEARCH_PATHS = ( -- "../../../out/ios-release/filament/lib/$(CURRENT_ARCH)", -- "../../../out/ios-release/filament/lib/universal", -- ); - OTHER_LDFLAGS = ( -- "-lfilament", -- "-lbackend", -- "-lfilaflat", -- "-lktxreader", -- "-lfilabridge", -- "-lutils", -- "-lsmol-v", -- "-lgeometry", -- "-libl", -- "-lfilamat", -- "-force_load ../../../out/ios-debug/filament/lib/arm64/libbackend_test.a", -+ "-force_load ../../../out/ios-release/filament/lib/arm64/libbackend_test.xcframework", - ); - PRODUCT_BUNDLE_IDENTIFIER = "${SAMPLE_CODE_DISAMBIGUATOR}.google.filament.backend-test"; - SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; -@@ -428,6 +510,11 @@ - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; -+ FRAMEWORK_SEARCH_PATHS = ( -+ "$(inherited)", -+ "\"$(SRCROOT)/../../../out/ios-release/filament/lib\"", -+ "\"../../../out/ios-release/filament/lib\"", -+ ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "FILAMENT_APP_USE_METAL=1", - "$(inherited)", -@@ -442,22 +529,8 @@ - "$(inherited)", - "@executable_path/Frameworks", - ); -- LIBRARY_SEARCH_PATHS = ( -- "../../../out/ios-debug/filament/lib/$(CURRENT_ARCH)", -- "../../../out/ios-debug/filament/lib/universal", -- ); - OTHER_LDFLAGS = ( -- "-lfilament", -- "-lbackend", -- "-lfilaflat", -- "-lktxreader", -- "-lfilabridge", -- "-lutils", -- "-lsmol-v", -- "-lgeometry", -- "-libl", -- "-lfilamat", -- "-force_load ../../../out/ios-debug/filament/lib/arm64/libbackend_test.a", -+ "-force_load ../../../out/ios-release/filament/lib/arm64/libbackend_test.xcframework", - ); - PRODUCT_BUNDLE_IDENTIFIER = "${SAMPLE_CODE_DISAMBIGUATOR}.google.filament.backend-test"; - SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; -@@ -473,6 +546,11 @@ - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; -+ FRAMEWORK_SEARCH_PATHS = ( -+ "$(inherited)", -+ "\"$(SRCROOT)/../../../out/ios-release/filament/lib\"", -+ "\"../../../out/ios-release/filament/lib\"", -+ ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "FILAMENT_APP_USE_METAL=1", - "$(inherited)", -@@ -487,22 +565,8 @@ - "$(inherited)", - "@executable_path/Frameworks", - ); -- LIBRARY_SEARCH_PATHS = ( -- "../../../out/ios-release/filament/lib/$(CURRENT_ARCH)", -- "../../../out/ios-release/filament/lib/universal", -- ); - OTHER_LDFLAGS = ( -- "-lfilament", -- "-lbackend", -- "-lfilaflat", -- "-lktxreader", -- "-lfilabridge", -- "-lutils", -- "-lsmol-v", -- "-lgeometry", -- "-libl", -- "-lfilamat", -- "-force_load ../../../out/ios-debug/filament/lib/arm64/libbackend_test.a", -+ "-force_load ../../../out/ios-release/filament/lib/arm64/libbackend_test.xcframework", - ); - PRODUCT_BUNDLE_IDENTIFIER = "${SAMPLE_CODE_DISAMBIGUATOR}.google.filament.backend-test"; - SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; -diff --git a/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme b/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme -index f4026437b..d64627583 100644 ---- a/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme -+++ b/ios/samples/backend-test/backend-test.xcodeproj/xcshareddata/xcschemes/backend-test Metal.xcscheme -@@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Date: Sat, 10 Jan 2026 17:31:14 +0100 Subject: [PATCH 8/9] recompile materials --- .../scripts/compile-materials.sh | 2 +- .../Shared/assets/background_image.filamat | Bin 34017 -> 35271 bytes .../transparent_shadow_material.filamat | Bin 512786 -> 238373 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/AppExampleFabric/scripts/compile-materials.sh b/examples/AppExampleFabric/scripts/compile-materials.sh index 5fcc62ef..b0946681 100755 --- a/examples/AppExampleFabric/scripts/compile-materials.sh +++ b/examples/AppExampleFabric/scripts/compile-materials.sh @@ -3,7 +3,7 @@ set -e # Make sure the filament matc tool is available: -matc_path=../../../filament/out/cmake-release/tools/matc/matc +matc_path=../../filament/out/cmake-release/tools/matc/matc if [ ! -f "$matc_path" ]; then echo "The filament matc tool is missing. Did you build Filament properly?" exit 1 diff --git a/examples/Shared/assets/background_image.filamat b/examples/Shared/assets/background_image.filamat index cfaaafec2a0aceb69edb192aa25333bf4ba3db67..35c2bbae14721963b5b1a10e9b3eca83c58eb9ac 100644 GIT binary patch literal 35271 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 literal 34017 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 JW6k^G{{Z1fsU`pb literal 512786 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üSb-Q{}P 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 E0Z&&-uK)l5 From 03cfdd8071ff77349243820f62c98a55bcb5831e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Sun, 11 Jan 2026 12:00:17 +0100 Subject: [PATCH 9/9] fix bump bullet to latest + NDK 27 --- bullet3 | 2 +- package/android/gradle.properties | 8 ++--- package/scripts/BulletAndroid.mk | 57 +++++++++++++++++++++++++++++++ package/scripts/build-bullet3.sh | 16 +++++++-- 4 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 package/scripts/BulletAndroid.mk diff --git a/bullet3 b/bullet3 index 6bb8d112..63c4d67e 160000 --- a/bullet3 +++ b/bullet3 @@ -1 +1 @@ -Subproject commit 6bb8d1123d8a55d407b19fd3357c724d0f5c9d3c +Subproject commit 63c4d67e337017f9d8b298c900e9aabdb69296e7 diff --git a/package/android/gradle.properties b/package/android/gradle.properties index da4a8e42..e150252c 100644 --- a/package/android/gradle.properties +++ b/package/android/gradle.properties @@ -1,5 +1,5 @@ Filament_kotlinVersion=1.7.0 -Filament_minSdkVersion=21 -Filament_targetSdkVersion=31 -Filament_compileSdkVersion=31 -Filament_ndkversion=25.1.8937393 +Filament_minSdkVersion=24 +Filament_targetSdkVersion=35 +Filament_compileSdkVersion=35 +Filament_ndkversion=27.1.12297006 diff --git a/package/scripts/BulletAndroid.mk b/package/scripts/BulletAndroid.mk new file mode 100644 index 00000000..d9d9cee8 --- /dev/null +++ b/package/scripts/BulletAndroid.mk @@ -0,0 +1,57 @@ +LOCAL_PATH := ../../.. + +include $(CLEAR_VARS) + +# Disable profiling to avoid thread-local storage issues with NDK 27 +# Enable emulated TLS for all architectures to avoid __tls_get_addr linker errors +LOCAL_CFLAGS := $(LOCAL_C_INCLUDES:%=-I%) -DBT_THREADSAFE -DSCE_PFX_USE_SIMD_VECTORMATH -DBT_NO_PROFILE +# TODO: When we bump the mind SDK to >= 29, we can remove -femulated-tls +LOCAL_CFLAGS += -femulated-tls + +LOCAL_CFLAGS += -ffast-math -funsafe-math-optimizations + +# apply this to disable optimization +# TARGET_CFLAGS := $(TARGET_CFLAGS) -O0 + +# apply these 2 to turn on assembly output (*.c/*.cpp to *.s file) +#compile-cpp-source = $(eval $(call ev-compile-cpp-source,$1,$(1:%$(LOCAL_CPP_EXTENSION)=%.s))) +#TARGET_CFLAGS := $(TARGET_CFLAGS) -S + +# Enable or disable NEON. Don't forget to apply, or not apply, -mfpu=neon and -mfloat-abi=softfp +# flags in addition, e.g., if this is true both of those need to be included in LOCAL_CFLAGS +# to avoid the possibility that ndk-build will "forget" to add them on some files +LOCAL_ARM_NEON := true + +# Only apply ARM32-specific flags for armeabi-v7a (NDK 27+ compatibility) +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) + LOCAL_CFLAGS += -mfpu=neon -mfloat-abi=softfp +endif + +TARGET_CFLAGS := $(filter-out -ffpu=vfp,$(TARGET_CFLAGS)) + +# setup to build static library +LOCAL_MODULE := libBullet + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/src + +#find all the file recursively under jni/ +FILE_LIST := $(wildcard \ + $(LOCAL_PATH)/src/LinearMath/*.cpp \ + $(LOCAL_PATH)/src/Bullet3Common/*.cpp \ + $(LOCAL_PATH)/src/BulletCollision/BroadphaseCollision/*.cpp \ + $(LOCAL_PATH)/src/BulletCollision/CollisionDispatch/*.cpp \ + $(LOCAL_PATH)/src/BulletCollision/CollisionShapes/*.cpp \ + $(LOCAL_PATH)/src/BulletCollision/NarrowPhaseCollision/*.cpp \ + $(LOCAL_PATH)/src/BulletDynamics/ConstraintSolver/*.cpp \ + $(LOCAL_PATH)/src/BulletDynamics/Dynamics/*.cpp \ + $(LOCAL_PATH)/src/BulletDynamics/Featherstone/*.cpp \ + $(LOCAL_PATH)/src/BulletDynamics/MLCPSolvers/*.cpp \ + $(LOCAL_PATH)/src/BulletDynamics/Vehicle/*.cpp \ + $(LOCAL_PATH)/src/BulletDynamics/Character/*.cpp \ + $(LOCAL_PATH)/src/BulletSoftBody/*.cpp \ + $(LOCAL_PATH)/src/BulletInverseDynamics/*.cpp \ + $(LOCAL_PATH)/src/BulletInverseDynamics/details/*.cpp \ + ) +LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) + +include $(BUILD_STATIC_LIBRARY) diff --git a/package/scripts/build-bullet3.sh b/package/scripts/build-bullet3.sh index f2c025a2..9eec6f94 100755 --- a/package/scripts/build-bullet3.sh +++ b/package/scripts/build-bullet3.sh @@ -47,11 +47,18 @@ ANDROID_NDK_VERSION="$(grep '^Filament_ndkversion' ./android/gradle.properties | echo "Using target SDK: $TARGET_SDK" echo "Using NDK version: $ANDROID_NDK_VERSION" -# We need to copy over the updated BulletAndroid.mk file, since theirs is outdated +# We need to copy over the updated Bullet Android build files for NDK 27 compatibility cp -f scripts/BulletAndroidApplication.mk ../bullet3/build3/Android/jni/Application.mk +cp -f scripts/BulletAndroid.mk ../bullet3/build3/Android/jni/Android.mk # Change the {PLATFORM_NAME} to the actual platform value from gradle.properties sed -i '' "s/{PLATFORM_NAME}/$TARGET_SDK/g" ../bullet3/build3/Android/jni/Application.mk +# Temporarily rename VERSION file to avoid conflicts with C++ standard library headers +# (NDK 27+ includes header which conflicts with bullet3's VERSION file) +if [ -f ../bullet3/VERSION ]; then + mv ../bullet3/VERSION ../bullet3/VERSION.tmp +fi + cd ../bullet3/build3/Android/jni # Build the Bullet3 library $ANDROID_HOME/ndk/$ANDROID_NDK_VERSION/ndk-build @@ -61,7 +68,12 @@ cp -rf ../obj/local/* ../../../../package/android/libs/bullet3/lib # Clean all changes in bullet3 (the build files are not under gitignore) git checkout . # Discard all uncommitted changes -rm -rf ../obj/ # Remove untracked files and directories +rm -rf ../obj/ # Remove untracked files and directories + +# Restore VERSION file if it was renamed +if [ -f ../../../../bullet3/VERSION.tmp ]; then + mv ../../../../bullet3/VERSION.tmp ../../../../bullet3/VERSION +fi # Remove the objs folder cd ../../../../package/android/libs/bullet3/lib