From e901837317821d8862b219db3d0ee12900c2fb99 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Tue, 7 May 2024 15:38:43 -0700 Subject: [PATCH 01/20] Log excess buffer allocations for Metal --- filament/backend/src/metal/MetalBuffer.h | 11 +++++++++++ filament/backend/src/metal/MetalBuffer.mm | 1 + filament/backend/src/metal/MetalDriver.mm | 3 +++ 3 files changed, 15 insertions(+) diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 9dcd1b720ff..8586eeb845a 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -18,6 +18,7 @@ #define TNT_FILAMENT_DRIVER_METALBUFFER_H #include "MetalContext.h" +#include "MetalPlatform.h" #include @@ -34,6 +35,8 @@ namespace filament::backend { class TrackedMetalBuffer { public: + static constexpr size_t EXCESS_BUFFER_COUNT = 30000; + enum class Type { NONE = 0, GENERIC = 1, @@ -62,6 +65,12 @@ class TrackedMetalBuffer { if (buffer) { aliveBuffers[toIndex(type)]++; mType = type; + if (getAliveBuffers() >= EXCESS_BUFFER_COUNT) { + if (platform && platform->hasDebugUpdateStatFunc()) { + platform->debugUpdateStat("filament.metal.excess_buffers_allocated", + TrackedMetalBuffer::getAliveBuffers()); + } + } } } @@ -96,6 +105,7 @@ class TrackedMetalBuffer { assert_invariant(type != Type::NONE); return aliveBuffers[toIndex(type)]; } + static void setPlatform(MetalPlatform* p) { platform = p; } private: void swap(TrackedMetalBuffer& other) noexcept { @@ -106,6 +116,7 @@ class TrackedMetalBuffer { id mBuffer; Type mType = Type::NONE; + static MetalPlatform* platform; static std::array aliveBuffers; }; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index de45eb376ae..ec8a8878e2e 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -23,6 +23,7 @@ namespace backend { std::array TrackedMetalBuffer::aliveBuffers = { 0 }; +MetalPlatform* TrackedMetalBuffer::platform = nullptr; MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage, size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) { diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 19f0680ad2f..77ee6912dc0 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -105,6 +105,8 @@ driverConfig.disableHandleUseAfterFreeCheck) { mContext->driver = this; + TrackedMetalBuffer::setPlatform(platform); + mContext->device = mPlatform.createDevice(); assert_invariant(mContext->device); @@ -198,6 +200,7 @@ } MetalDriver::~MetalDriver() noexcept { + TrackedMetalBuffer::setPlatform(nullptr); mContext->device = nil; mContext->emptyTexture = nil; CFRelease(mContext->textureCache); From 744708b5ca20ec7c61ab886ca3a25ac7fa27f316 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 7 May 2024 11:28:14 -0700 Subject: [PATCH 02/20] remove the single usage we have the STL's stream headers can bring in a lot of code, we don't use them. --- filament/src/details/Material.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index de94d79192a..f43faf3b675 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -60,7 +60,7 @@ #include #include #include -#include +#include #include #include #include @@ -83,18 +83,18 @@ static std::unique_ptr createParser(Backend backend, MaterialParser::ParseResult const materialResult = materialParser->parse(); if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) { - std::stringstream languageNames; + std::string languageNames; for (auto it = languages.begin(); it != languages.end(); ++it) { - languageNames << shaderLanguageToString(*it); + languageNames.append(shaderLanguageToString(*it)); if (std::next(it) != languages.end()) { - languageNames << ", "; + languageNames.append(", "); } } ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND, "the material was not built for any of the %s backend's supported shader " "languages (%s)\n", - backendToString(backend), languageNames.str().c_str()); + backendToString(backend), languageNames.c_str()); } if (backend == Backend::NOOP) { From a1dea7b1fa6f903b692ef52b76f49630064901d5 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 8 May 2024 14:39:53 -0700 Subject: [PATCH 03/20] Metal: log slow buffer allocation times (#7834) --- filament/backend/src/metal/MetalBuffer.h | 41 ++++++++++++++++++- filament/backend/src/metal/MetalBuffer.mm | 8 +++- filament/backend/src/metal/MetalBufferPool.mm | 8 +++- filament/backend/src/metal/MetalDriver.mm | 2 + 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 8586eeb845a..081ca6c3fd0 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -29,9 +29,42 @@ #include #include #include +#include namespace filament::backend { +class ScopedAllocationTimer { +public: + ScopedAllocationTimer(const char* name) : mBeginning(clock_t::now()), mName(name) {} + ~ScopedAllocationTimer() { + using namespace std::literals::chrono_literals; + static constexpr std::chrono::seconds LONG_TIME_THRESHOLD = 10s; + + auto end = clock_t::now(); + std::chrono::duration allocationTimeMicroseconds = end - mBeginning; + + if (UTILS_UNLIKELY(allocationTimeMicroseconds > LONG_TIME_THRESHOLD)) { + if (platform && platform->hasDebugUpdateStatFunc()) { + char buffer[64]; + snprintf(buffer, sizeof(buffer), "filament.metal.long_buffer_allocation_time.%s", + mName); + platform->debugUpdateStat( + buffer, static_cast(allocationTimeMicroseconds.count())); + } + } + } + + static void setPlatform(MetalPlatform* p) { platform = p; } + +private: + typedef std::chrono::steady_clock clock_t; + + static MetalPlatform* platform; + + std::chrono::time_point mBeginning; + const char* mName; +}; + class TrackedMetalBuffer { public: @@ -220,6 +253,7 @@ class MetalRingBuffer { mBufferOptions(options), mSlotSizeBytes(computeSlotSize(layout)), mSlotCount(slotCount) { + ScopedAllocationTimer timer("ring"); mBuffer = { [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions], TrackedMetalBuffer::Type::RING }; assert_invariant(mBuffer); @@ -239,8 +273,11 @@ class MetalRingBuffer { // If we already have an aux buffer, it will get freed here, unless it has been retained // by a MTLCommandBuffer. In that case, it will be freed when the command buffer // finishes executing. - mAuxBuffer = { [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions], - TrackedMetalBuffer::Type::RING }; + { + ScopedAllocationTimer timer("ring"); + mAuxBuffer = { [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions], + TrackedMetalBuffer::Type::RING }; + } assert_invariant(mAuxBuffer); return { mAuxBuffer.get(), 0 }; } diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index ec8a8878e2e..5f09a290781 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -24,6 +24,7 @@ std::array TrackedMetalBuffer::aliveBuffers = { 0 }; MetalPlatform* TrackedMetalBuffer::platform = nullptr; +MetalPlatform* ScopedAllocationTimer::platform = nullptr; MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage, size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) { @@ -38,8 +39,11 @@ } // Otherwise, we allocate a private GPU buffer. - mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate], - TrackedMetalBuffer::Type::GENERIC }; + { + ScopedAllocationTimer timer("generic"); + mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate], + TrackedMetalBuffer::Type::GENERIC }; + } ASSERT_POSTCONDITION(mBuffer, "Could not allocate Metal buffer of size %zu.", size); } diff --git a/filament/backend/src/metal/MetalBufferPool.mm b/filament/backend/src/metal/MetalBufferPool.mm index 911bf84e4ac..a1e54a46239 100644 --- a/filament/backend/src/metal/MetalBufferPool.mm +++ b/filament/backend/src/metal/MetalBufferPool.mm @@ -42,8 +42,12 @@ } // We were not able to find a sufficiently large stage, so create a new one. - id buffer = [mContext.device newBufferWithLength:numBytes - options:MTLResourceStorageModeShared]; + id buffer = nil; + { + ScopedAllocationTimer timer("staging"); + buffer = [mContext.device newBufferWithLength:numBytes + options:MTLResourceStorageModeShared]; + } ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes); MetalBufferPoolEntry* stage = new MetalBufferPoolEntry { .buffer = { buffer, TrackedMetalBuffer::Type::STAGING }, diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 77ee6912dc0..1149e2b644c 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -106,6 +106,7 @@ mContext->driver = this; TrackedMetalBuffer::setPlatform(platform); + ScopedAllocationTimer::setPlatform(platform); mContext->device = mPlatform.createDevice(); assert_invariant(mContext->device); @@ -201,6 +202,7 @@ MetalDriver::~MetalDriver() noexcept { TrackedMetalBuffer::setPlatform(nullptr); + ScopedAllocationTimer::setPlatform(nullptr); mContext->device = nil; mContext->emptyTexture = nil; CFRelease(mContext->textureCache); From 1b38cda0d51f0648e6e488b196851b48ca56d824 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 8 May 2024 14:42:40 -0700 Subject: [PATCH 04/20] Add preferredShaderLanguage Java bindings (#7835) --- .../filament-android/src/main/cpp/Engine.cpp | 4 +++- .../com/google/android/filament/Engine.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 482d54834b3..a4ace378534 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -518,6 +518,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu jint stereoscopicType, jlong stereoscopicEyeCount, jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge, jboolean disableHandleUseAfterFreeCheck, + jint preferredShaderLanguage, jboolean forceGLES2Context) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; Engine::Config config = { @@ -534,7 +535,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu .resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB, .resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge, .disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck, - .forceGLES2Context = (bool) forceGLES2Context + .preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage, + .forceGLES2Context = (bool) forceGLES2Context, }; builder->config(&config); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index bfb17aa5ae4..8d53c412b45 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -226,6 +226,7 @@ public Builder config(Config config) { config.stereoscopicType.ordinal(), config.stereoscopicEyeCount, config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge, config.disableHandleUseAfterFreeCheck, + config.preferredShaderLanguage.ordinal(), config.forceGLES2Context); return this; } @@ -430,6 +431,28 @@ public static class Config { */ public boolean 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. + */ + public enum ShaderLanguage { + DEFAULT, + MSL, + METAL_LIBRARY, + }; + public 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 @@ -1362,6 +1385,7 @@ private static native void nSetBuilderConfig(long nativeBuilder, long commandBuf int stereoscopicType, long stereoscopicEyeCount, long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge, boolean disableHandleUseAfterFreeCheck, + int preferredShaderLanguage, boolean forceGLES2Context); private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal); private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext); From d87f9b621bd0784809c4afdd1f5ef36ebab7a3bc Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 9 May 2024 05:11:31 -0700 Subject: [PATCH 05/20] add a simple tranformmanager unit test this was to test issue #7827 --- filament/test/filament_test.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/filament/test/filament_test.cpp b/filament/test/filament_test.cpp index 5f47b0abee9..7bd3d3445ab 100644 --- a/filament/test/filament_test.cpp +++ b/filament/test/filament_test.cpp @@ -179,6 +179,23 @@ TEST(FilamentTest, SkinningMath) { } } +TEST(FilamentTest, TransformManagerSimple) { + filament::FTransformManager tcm; + EntityManager& em = EntityManager::get(); + Entity root = em.create(); + tcm.create(root); + + auto ti = tcm.getInstance(root); + + auto t = mat4f::translation(float3{ 1, 2, 3 }); + auto prev = tcm.getWorldTransform(ti); + tcm.setTransform(ti, t); + auto updated = tcm.getWorldTransform(ti); + + EXPECT_NE(prev, t); + EXPECT_EQ(updated, t); +} + TEST(FilamentTest, TransformManager) { filament::FTransformManager tcm; tcm.setAccurateTranslationsEnabled(true); From ad60008b6a8b07cc6777624855d1ddeb8917ec7b Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 9 May 2024 10:49:41 -0700 Subject: [PATCH 06/20] ryanmyers: Improve logging for Metallib function lookup failures (#7836) If a .metallib was compiled with a target iOS version that's newer than the current device, loading the .metallib may succeed, but finding main0 (or any other function in it) will fail. Currently, this causes a crash due to an assert. Logging the error and returning MetalFunctionBundle::error() makes the crash slightly easier to diagnose. (Note that in practice, this will probably be a useless "Compiler encountered an internal error" message -- the GPU backend is crashing, and the Metal stub library sees XPC_ERROR_CONNECTION_INTERRUPTED. It retries up to 3 times (crashing each time) and then gives up.) --- .../backend/src/metal/MetalShaderCompiler.mm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index 1aa02f9c870..90d6a47721c 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -169,6 +169,23 @@ bool isReady() const noexcept { id function = [library newFunctionWithName:@"main0" constantValues:constants error:&error]; + if (function == nil) { + // If the library loads but functions within it fail to load, it usually means the + // GPU backend crashed. (This can happen if it's a Metallib shader that was compiled + // with a minimum iOS version that's newer than this device.) + NSString* errorMessage = @"unknown error"; + if (error) { + auto description = + [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; + utils::slog.w << description << utils::io::endl; + errorMessage = error.localizedDescription; + } + PANIC_LOG("Failed to load main0 in Metal program."); + NSString* programName = + [NSString stringWithFormat:@"%s::main0", program.getName().c_str_safe()]; + return MetalFunctionBundle::error(errorMessage, programName); + } + if (!program.getName().empty()) { function.label = @(program.getName().c_str()); } From 6f2c45c76d9aa51da1468fe9e47cba5778657850 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 9 May 2024 16:14:03 -0700 Subject: [PATCH 07/20] Add push constants (#7817) - Push constants is a small set of bytes that can be recorded directly on the command buffer. - Implemented it for the vulkan/gl backend. --- .../backend/include/backend/DriverEnums.h | 10 ++- filament/backend/include/backend/Program.h | 18 +++++ .../include/private/backend/DriverAPI.inc | 5 ++ filament/backend/src/Program.cpp | 6 ++ filament/backend/src/metal/MetalDriver.mm | 3 + filament/backend/src/noop/NoopDriver.cpp | 4 ++ filament/backend/src/opengl/OpenGLDriver.cpp | 46 +++++++++++++ filament/backend/src/opengl/OpenGLDriver.h | 4 +- filament/backend/src/opengl/OpenGLProgram.cpp | 20 +++++- filament/backend/src/opengl/OpenGLProgram.h | 29 +++++++-- filament/backend/src/vulkan/VulkanDriver.cpp | 20 +++++- filament/backend/src/vulkan/VulkanDriver.h | 7 ++ filament/backend/src/vulkan/VulkanHandles.cpp | 65 +++++++++++++++++-- filament/backend/src/vulkan/VulkanHandles.h | 48 ++++++++++++-- .../caching/VulkanDescriptorSetManager.cpp | 2 +- .../caching/VulkanDescriptorSetManager.h | 5 +- .../caching/VulkanPipelineLayoutCache.cpp | 33 ++++++++-- .../caching/VulkanPipelineLayoutCache.h | 22 ++++++- filament/src/MaterialParser.cpp | 51 +++++++++++++++ filament/src/MaterialParser.h | 8 +++ filament/src/details/Material.cpp | 42 +++++++++++- filament/src/details/Material.h | 7 ++ .../include/filament/MaterialChunkType.h | 1 + .../include/private/filament/EngineEnums.h | 5 ++ .../private/filament/PushConstantInfo.h | 43 ++++++++++++ libs/filamat/CMakeLists.txt | 3 +- .../filamat/include/filamat/MaterialBuilder.h | 13 ++++ libs/filamat/src/MaterialBuilder.cpp | 28 +++++++- libs/filamat/src/PushConstantDefinitions.h | 44 +++++++++++++ .../src/eiff/MaterialInterfaceBlockChunk.cpp | 19 ++++++ .../src/eiff/MaterialInterfaceBlockChunk.h | 16 +++++ libs/filamat/src/shaders/CodeGenerator.cpp | 43 +++++++++++- libs/filamat/src/shaders/CodeGenerator.h | 7 +- libs/filamat/src/shaders/ShaderGenerator.cpp | 23 +++++-- libs/filamat/src/shaders/ShaderGenerator.h | 2 + 35 files changed, 659 insertions(+), 43 deletions(-) create mode 100644 libs/filabridge/include/private/filament/PushConstantInfo.h create mode 100644 libs/filamat/src/PushConstantDefinitions.h diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 969632c327a..490b889fa2e 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -31,6 +31,7 @@ #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 @@ -91,12 +92,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 { @@ -332,7 +336,7 @@ enum class UniformType : uint8_t { /** * Supported constant parameter types */ - enum class ConstantType : uint8_t { +enum class ConstantType : uint8_t { INT, FLOAT, BOOL @@ -1219,6 +1223,8 @@ struct StencilState { uint8_t padding = 0; }; +using PushConstantVariant = std::variant; + static_assert(sizeof(StencilState::StencilOperations) == 5u, "StencilOperations size not what was intended"); diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index b5c1dd9babd..7cec72cd0c2 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -117,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; @@ -148,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; } @@ -165,6 +182,7 @@ class Program { 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/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index f729a370258..3e13c4c3547 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -434,6 +434,11 @@ DECL_DRIVER_API_N(bindSamplers, uint32_t, index, backend::SamplerGroupHandle, sbh) +DECL_DRIVER_API_N(setPushConstant, + backend::ShaderStage, stage, + uint8_t, index, + backend::PushConstantVariant, value) + DECL_DRIVER_API_N(insertEventMarker, const char*, string, uint32_t, len = 0) diff --git a/filament/backend/src/Program.cpp b/filament/backend/src/Program.cpp index 39a941485e8..53bfa5a7a15 100644 --- a/filament/backend/src/Program.cpp +++ b/filament/backend/src/Program.cpp @@ -91,6 +91,12 @@ Program& Program::specializationConstants( return *this; } +Program& Program::pushConstants(ShaderStage stage, + utils::FixedCapacityVector constants) noexcept { + mPushConstants[static_cast(stage)] = std::move(constants); + return *this; +} + Program& Program::cacheId(uint64_t cacheId) noexcept { mCacheId = cacheId; return *this; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 1149e2b644c..62cba820401 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -1252,6 +1252,9 @@ mContext->samplerBindings[index] = sb; } +void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant value) {} + void MetalDriver::insertEventMarker(const char* string, uint32_t len) { } diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 9984bed9a68..9b3b21f168a 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -312,6 +312,10 @@ void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) { void NoopDriver::bindSamplers(uint32_t index, Handle sbh) { } +void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant value) { +} + void NoopDriver::insertEventMarker(char const* string, uint32_t len) { } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 52b75fcf166..d136a63c95a 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -269,6 +269,10 @@ void OpenGLDriver::terminate() { assert_invariant(mGpuCommandCompleteOps.empty()); #endif + if (mCurrentPushConstants) { + delete mCurrentPushConstants; + } + mContext.terminate(); mPlatform.terminate(); @@ -289,6 +293,42 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept { mContext.bindSampler(unit, sampler); } +void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant value) { + assert_invariant(mCurrentPushConstants && + "Calling setPushConstant() before binding a pipeline"); + + assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT); + utils::Slice> constants; + if (stage == ShaderStage::VERTEX) { + constants = mCurrentPushConstants->vertexConstants; + } else if (stage == ShaderStage::FRAGMENT) { + constants = mCurrentPushConstants->fragmentConstants; + } + + assert_invariant(index < constants.size()); + auto const& [location, type] = constants[index]; + + // This push constant wasn't found in the shader. It's ok to return without error-ing here. + if (location < 0) { + return; + } + + if (std::holds_alternative(value)) { + assert_invariant(type == ConstantType::BOOL); + bool const bval = std::get(value); + glUniform1i(location, bval ? 1 : 0); + } else if (std::holds_alternative(value)) { + assert_invariant(type == ConstantType::FLOAT); + float const fval = std::get(value); + glUniform1f(location, fval); + } else { + assert_invariant(type == ConstantType::INT); + int const ival = std::get(value); + glUniform1i(location, ival); + } +} + void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept { assert_invariant(t != nullptr); mContext.bindTexture(unit, t->gl.target, t->gl.id); @@ -3808,6 +3848,12 @@ void OpenGLDriver::bindPipeline(PipelineState state) { gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant); OpenGLProgram* const p = handle_cast(state.program); mValidProgram = useProgram(p); + + if (!mCurrentPushConstants) { + mCurrentPushConstants = new (std::nothrow) PushConstantBundle{p->getPushConstants()}; + } else { + (*mCurrentPushConstants) = p->getPushConstants(); + } } void OpenGLDriver::bindRenderPrimitive(Handle rph) { diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index 06befbeffff..667262b715e 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -66,9 +66,9 @@ namespace filament::backend { class OpenGLPlatform; class PixelBufferDescriptor; struct TargetBufferInfo; - class OpenGLProgram; class TimerQueryFactoryInterface; +struct PushConstantBundle; class OpenGLDriver final : public DriverBase { inline explicit OpenGLDriver(OpenGLPlatform* platform, @@ -375,6 +375,8 @@ class OpenGLDriver final : public DriverBase { // for ES2 sRGB support GLSwapChain* mCurrentDrawSwapChain = nullptr; bool mRec709OutputColorspace = false; + + PushConstantBundle* mCurrentPushConstants = nullptr; }; // ------------------------------------------------------------------------------------------------ diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index 0d3f7a1a4b8..6c5eab9a442 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -46,6 +46,8 @@ struct OpenGLProgram::LazyInitializationData { Program::UniformBlockInfo uniformBlockInfo; Program::SamplerGroupInfo samplerGroupInfo; std::array bindingUniformInfo; + utils::FixedCapacityVector vertexPushConstants; + utils::FixedCapacityVector fragmentPushConstants; }; @@ -53,7 +55,6 @@ OpenGLProgram::OpenGLProgram() noexcept = default; OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept : HwProgram(std::move(program.getName())) { - auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData(); lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo()); if (UTILS_UNLIKELY(gld.getContext().isES2())) { @@ -61,6 +62,8 @@ OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept } else { lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings()); } + lazyInitializationData->vertexPushConstants = std::move(program.getPushConstants(ShaderStage::VERTEX)); + lazyInitializationData->fragmentPushConstants = std::move(program.getPushConstants(ShaderStage::FRAGMENT)); ShaderCompilerService& compiler = gld.getShaderCompilerService(); mToken = compiler.createProgram(name, std::move(program)); @@ -203,6 +206,21 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra } } mUsedBindingsCount = usedBindingCount; + + auto& vertexConstants = lazyInitializationData.vertexPushConstants; + auto& fragmentConstants = lazyInitializationData.fragmentPushConstants; + + size_t const totalConstantCount = vertexConstants.size() + fragmentConstants.size(); + if (totalConstantCount > 0) { + mPushConstants.reserve(totalConstantCount); + mPushConstantFragmentStageOffset = vertexConstants.size(); + auto const transformAndAdd = [&](Program::PushConstant const& constant) { + GLint const loc = glGetUniformLocation(program, constant.name.c_str()); + mPushConstants.push_back({loc, constant.type}); + }; + std::for_each(vertexConstants.cbegin(), vertexConstants.cend(), transformAndAdd); + std::for_each(fragmentConstants.cbegin(), fragmentConstants.cend(), transformAndAdd); + } } void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept { diff --git a/filament/backend/src/opengl/OpenGLProgram.h b/filament/backend/src/opengl/OpenGLProgram.h index 8b9d2c400b5..19be485ac6b 100644 --- a/filament/backend/src/opengl/OpenGLProgram.h +++ b/filament/backend/src/opengl/OpenGLProgram.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -38,6 +39,11 @@ namespace filament::backend { class OpenGLDriver; +struct PushConstantBundle { + utils::Slice> vertexConstants; + utils::Slice> fragmentConstants; +}; + class OpenGLProgram : public HwProgram { public: @@ -78,6 +84,14 @@ class OpenGLProgram : public HwProgram { GLuint program = 0; } gl; // 4 bytes + PushConstantBundle getPushConstants() { + auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset; + return { + .vertexConstants = utils::Slice(mPushConstants.begin(), fragBegin), + .fragmentConstants = utils::Slice(fragBegin, mPushConstants.end()), + }; + } + private: // keep these away from of other class attributes struct LazyInitializationData; @@ -95,11 +109,14 @@ class OpenGLProgram : public HwProgram { ShaderCompilerService::program_token_t mToken{}; // 16 bytes uint8_t mUsedBindingsCount = 0u; // 1 byte - UTILS_UNUSED uint8_t padding[3] = {}; // 3 bytes + UTILS_UNUSED uint8_t padding[2] = {}; // 2 byte + // Push constant array offset for fragment stage constants. + uint8_t mPushConstantFragmentStageOffset = 0u; // 1 byte // only needed for ES2 - GLint mRec709Location = -1; // 4 bytes + GLint mRec709Location = -1; // 4 bytes + using LocationInfo = utils::FixedCapacityVector; struct UniformsRecord { Program::UniformInfo uniforms; @@ -107,11 +124,15 @@ class OpenGLProgram : public HwProgram { mutable GLuint id = 0; mutable uint16_t age = std::numeric_limits::max(); }; - UniformsRecord const* mUniformsRecords = nullptr; + UniformsRecord const* mUniformsRecords = nullptr; // 8 bytes + + // Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the + // size of the container to 9 bytes if there is a need in the future. + utils::FixedCapacityVector> mPushConstants;// 16 bytes }; // if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket. -static_assert(sizeof(OpenGLProgram) <= 64); // currently 48 bytes +static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 49e6f581c9f..1cdc8a20cc0 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -259,8 +259,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture, mEmptyBufferObject); - mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts) { - return mPipelineLayoutCache.getLayout(layouts); + mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts, VulkanProgram* program) { + return mPipelineLayoutCache.getLayout(layouts, program); }; } @@ -1572,6 +1572,14 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle sbh) { mSamplerBindings[index] = hwsb; } +void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant value) { + assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants"); + VulkanCommands* commands = &mCommands; + mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index, + value); +} + void VulkanDriver::insertEventMarker(char const* string, uint32_t len) { #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) mCommands.insertEventMarker(string, len); @@ -1857,7 +1865,13 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) { mDescriptorSetManager.updateSampler({}, binding, texture, vksampler); } - mPipelineCache.bindLayout(mDescriptorSetManager.bind(commands, program, mGetPipelineFunction)); + auto const pipelineLayout = mDescriptorSetManager.bind(commands, program, mGetPipelineFunction); + mBoundPipeline = { + .program = program, + .pipelineLayout = pipelineLayout, + }; + + mPipelineCache.bindLayout(pipelineLayout); mPipelineCache.bindPipeline(commands); FVK_SYSTRACE_END(); } diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index 8de0ae4a26e..2ededd06c2c 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -160,6 +160,13 @@ class VulkanDriver final : public DriverBase { VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction; + // This is necessary for us to write to push constants after binding a pipeline. + struct BoundPipeline { + VulkanProgram* program; + VkPipelineLayout pipelineLayout; + }; + BoundPipeline mBoundPipeline = {}; + RenderPassFboBundle mRenderPassFboInfo; bool const mIsSRGBSwapChainSupported; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 0dd11ce8d1d..40157753b8e 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -112,27 +112,82 @@ inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device, return layout; } +inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) { + switch(stage) { + case backend::ShaderStage::VERTEX: + return VK_SHADER_STAGE_VERTEX_BIT; + case backend::ShaderStage::FRAGMENT: + return VK_SHADER_STAGE_FRAGMENT_BIT; + case backend::ShaderStage::COMPUTE: + PANIC_POSTCONDITION("Unsupported stage"); + } +} + } // anonymous namespace -VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info, - Bitmask const& bitmask) +VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device, + VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask) : VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), mDevice(device), vklayout(createDescriptorSetLayout(device, info)), bitmask(bitmask), bindings(getBindings(bitmask)), - count(Count::fromLayoutBitmask(bitmask)) { -} + count(Count::fromLayoutBitmask(bitmask)) {} VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() { vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC); } +PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept { + mRangeCount = 0; + for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) { + auto const& constants = program.getPushConstants(stage); + if (constants.empty()) { + continue; + } + + // We store the type of the constant for type-checking when writing. + auto& types = mTypes[(uint8_t) stage]; + types.reserve(constants.size()); + std::for_each(constants.cbegin(), constants.cend(), [&types] (Program::PushConstant t) { + types.push_back(t.type); + }); + + mRanges[mRangeCount++] = { + .stageFlags = getVkStage(stage), + .offset = 0, + .size = (uint32_t) constants.size() * ENTRY_SIZE, + }; + } +} + +void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout, + backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) { + VulkanCommandBuffer* cmdbuf = &(commands->get()); + uint32_t binaryValue = 0; + UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage]; + if (std::holds_alternative(value)) { + assert_invariant(types[index] == ConstantType::BOOL); + bool const bval = std::get(value); + binaryValue = static_cast(bval ? VK_TRUE : VK_FALSE); + } else if (std::holds_alternative(value)) { + assert_invariant(types[index] == ConstantType::FLOAT); + float const fval = std::get(value); + binaryValue = *reinterpret_cast(&fval); + } else { + assert_invariant(types[index] == ConstantType::INT); + int const ival = std::get(value); + binaryValue = *reinterpret_cast(&ival); + } + vkCmdPushConstants(cmdbuf->buffer(), layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE, + &binaryValue); +} + VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept : HwProgram(builder.getName()), VulkanResource(VulkanResourceType::PROGRAM), - mInfo(new PipelineInfo()), + mInfo(new(std::nothrow) PipelineInfo(builder)), mDevice(device) { constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES; diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index bb20097a474..75596ea93cd 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -26,10 +26,10 @@ #include "VulkanTexture.h" #include "VulkanUtility.h" -#include "private/backend/SamplerGroup.h" -#include "utils/FixedCapacityVector.h" -#include "vulkan/vulkan_core.h" +#include +#include +#include #include #include @@ -180,6 +180,28 @@ struct VulkanDescriptorSet : public VulkanResource { using VulkanDescriptorSetList = std::array, VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; +using PushConstantNameArray = utils::FixedCapacityVector; +using PushConstantNameByStage = std::array; + +struct PushConstantDescription { + + explicit PushConstantDescription(backend::Program const& program) noexcept; + + VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; } + + uint32_t getVkRangeCount() const noexcept { return mRangeCount; } + + void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage, + uint8_t index, backend::PushConstantVariant const& value); + +private: + static constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t); + + utils::FixedCapacityVector mTypes[Program::SHADER_TYPE_COUNT]; + VkPushConstantRange mRanges[Program::SHADER_TYPE_COUNT]; + uint32_t mRangeCount; +}; + struct VulkanProgram : public HwProgram, VulkanResource { using BindingList = CappedArray; @@ -212,6 +234,19 @@ struct VulkanProgram : public HwProgram, VulkanResource { VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; } + inline uint32_t getPushConstantRangeCount() const { + return mInfo->pushConstantDescription.getVkRangeCount(); + } + + inline VkPushConstantRange const* getPushConstantRanges() const { + return mInfo->pushConstantDescription.getVkRanges(); + } + + inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout, + backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) { + mInfo->pushConstantDescription.write(commands, layout, stage, index, value); + } + #if FVK_ENABLED_DEBUG_SAMPLER_NAME inline utils::FixedCapacityVector const& getBindingToName() const { return mInfo->bindingToName; @@ -224,8 +259,9 @@ struct VulkanProgram : public HwProgram, VulkanResource { private: struct PipelineInfo { - PipelineInfo() - : bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) + explicit PipelineInfo(backend::Program const& program) noexcept + : bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff), + pushConstantDescription(program) #if FVK_ENABLED_DEBUG_SAMPLER_NAME , bindingToName(MAX_SAMPLER_COUNT, "") #endif @@ -241,6 +277,8 @@ struct VulkanProgram : public HwProgram, VulkanResource { // descset::DescriptorSetLayout layout; LayoutDescriptionList layouts; + PushConstantDescription pushConstantDescription; + #if FVK_ENABLED_DEBUG_SAMPLER_NAME // We store the sampler name mapped from binding index (only for debug purposes). utils::FixedCapacityVector bindingToName; diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp index 35228e95e6a..8c27d0e11de 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp @@ -879,7 +879,7 @@ class VulkanDescriptorSetManager::Impl { vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr); } - VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(outLayouts); + VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(outLayouts, program); VkCommandBuffer const cmdbuffer = commands->buffer(); BoundState state{}; diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h index 2fa0b020fe1..dbb969080aa 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h @@ -45,8 +45,8 @@ class VulkanDescriptorSetManager { public: static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; - using GetPipelineLayoutFunction = - std::function; + using GetPipelineLayoutFunction = std::function; VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator); @@ -108,3 +108,4 @@ class VulkanDescriptorSetManager { }// namespace filament::backend #endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H + diff --git a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp index ec96418aaf1..861e840bed2 100644 --- a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp +++ b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp @@ -21,13 +21,34 @@ namespace filament::backend { VkPipelineLayout VulkanPipelineLayoutCache::getLayout( - VulkanDescriptorSetLayoutList const& descriptorSetLayouts) { - PipelineLayoutKey key = {VK_NULL_HANDLE}; + VulkanDescriptorSetLayoutList const& descriptorSetLayouts, VulkanProgram* program) { + PipelineLayoutKey key = {}; uint8_t descSetLayoutCount = 0; for (auto layoutHandle: descriptorSetLayouts) { if (layoutHandle) { auto layout = mAllocator->handle_cast(layoutHandle); - key[descSetLayoutCount++] = layout->vklayout; + key.descSetLayouts[descSetLayoutCount++] = layout->vklayout; + } + } + + // build the push constant layout key + uint32_t pushConstantRangeCount = program->getPushConstantRangeCount(); + auto const& pushCostantRanges = program->getPushConstantRanges(); + if (pushConstantRangeCount > 0) { + assert_invariant(pushConstantRangeCount <= Program::SHADER_TYPE_COUNT); + for (uint8_t i = 0; i < pushConstantRangeCount; ++i) { + auto const& range = pushCostantRanges[i]; + auto& pushConstant = key.pushConstant[i]; + if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) { + pushConstant.stage = static_cast(ShaderStage::VERTEX); + } + if (range.stageFlags & VK_SHADER_STAGE_FRAGMENT_BIT) { + pushConstant.stage = static_cast(ShaderStage::FRAGMENT); + } + if (range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) { + pushConstant.stage = static_cast(ShaderStage::COMPUTE); + } + pushConstant.size = range.size; } } @@ -42,9 +63,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout( .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, .setLayoutCount = (uint32_t) descSetLayoutCount, - .pSetLayouts = key.data(), - .pushConstantRangeCount = 0, + .pSetLayouts = key.descSetLayouts.data(), + .pushConstantRangeCount = pushConstantRangeCount, + .pPushConstantRanges = pushCostantRanges, }; + VkPipelineLayout layout; vkCreatePipelineLayout(mDevice, &info, VKALLOC, &layout); diff --git a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h index 375e6124d23..42bd2926acd 100644 --- a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h +++ b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h @@ -35,13 +35,29 @@ class VulkanPipelineLayoutCache { void terminate() noexcept; - using PipelineLayoutKey = std::array; + struct PushConstantKey { + uint8_t stage;// We have one set of push constant per shader stage (fragment, vertex, etc). + uint8_t size; + // Note that there is also an offset parameter for push constants, but + // we always assume our update range will have the offset 0. + }; + + struct PipelineLayoutKey { + using DescriptorSetLayoutArray = std::array; + DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3 + PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3 + uint16_t padding = 0; + }; + static_assert(sizeof(PipelineLayoutKey) == 32); VulkanPipelineLayoutCache(VulkanPipelineLayoutCache const&) = delete; VulkanPipelineLayoutCache& operator=(VulkanPipelineLayoutCache const&) = delete; - VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts); + // A pipeline layout depends on the descriptor set layout and the push constant ranges, which + // are described in the program. + VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts, + VulkanProgram* program); private: using Timestamp = uint64_t; diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index b5aeda47cde..0304253b905 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -225,6 +226,14 @@ bool MaterialParser::getConstants(utils::FixedCapacityVector* return ChunkMaterialConstants::unflatten(unflattener, value); } +bool MaterialParser::getPushConstants(utils::CString* structVarName, + utils::FixedCapacityVector* value) const noexcept { + auto [start, end] = mImpl.mChunkContainer.getChunkRange(filamat::MaterialPushConstants); + if (start == end) return false; + Unflattener unflattener(start, end); + return ChunkMaterialPushConstants::unflatten(unflattener, structVarName, value); +} + bool MaterialParser::getDepthWriteSet(bool* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialDepthWriteSet, value); } @@ -709,4 +718,46 @@ bool ChunkMaterialConstants::unflatten(filaflat::Unflattener& unflattener, return true; } +bool ChunkMaterialPushConstants::unflatten(filaflat::Unflattener& unflattener, + utils::CString* structVarName, + utils::FixedCapacityVector* materialPushConstants) { + assert_invariant(materialPushConstants); + + if (!unflattener.read(structVarName)) { + return false; + } + + // Read number of constants. + uint64_t numConstants = 0; + if (!unflattener.read(&numConstants)) { + return false; + } + + materialPushConstants->reserve(numConstants); + materialPushConstants->resize(numConstants); + + for (uint64_t i = 0; i < numConstants; i++) { + CString constantName; + uint8_t constantType = 0; + uint8_t shaderStage = 0; + + if (!unflattener.read(&constantName)) { + return false; + } + + if (!unflattener.read(&constantType)) { + return false; + } + + if (!unflattener.read(&shaderStage)) { + return false; + } + + (*materialPushConstants)[i].name = constantName; + (*materialPushConstants)[i].type = static_cast(constantType); + (*materialPushConstants)[i].stage = static_cast(shaderStage); + } + return true; +} + } // namespace filament diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 2ec7f7c1cc4..b10a549d8b3 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -47,6 +47,7 @@ class BufferInterfaceBlock; class SamplerInterfaceBlock; struct SubpassInfo; struct MaterialConstant; +struct MaterialPushConstant; class MaterialParser { public: @@ -79,6 +80,8 @@ class MaterialParser { bool getSamplerBlockBindings(SamplerGroupBindingInfoList* pSamplerGroupInfoList, SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept; bool getConstants(utils::FixedCapacityVector* value) const noexcept; + bool getPushConstants(utils::CString* structVarName, + utils::FixedCapacityVector* value) const noexcept; using BindingUniformInfoContainer = utils::FixedCapacityVector< std::pair>; @@ -214,6 +217,11 @@ struct ChunkMaterialConstants { utils::FixedCapacityVector* materialConstants); }; +struct ChunkMaterialPushConstants { + static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName, + utils::FixedCapacityVector* materialPushConstants); +}; + } // namespace filament #endif // TNT_FILAMENT_MATERIALPARSER_H diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index f43faf3b675..80011d7f318 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -57,7 +58,6 @@ #include #include #include -#include #include #include #include @@ -298,6 +298,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder, processBlendingMode(parser); processSpecializationConstants(engine, builder, parser); + processPushConstants(engine, parser); processDepthVariants(engine, parser); // we can only initialize the default instance once we're initialized ourselves @@ -576,6 +577,9 @@ Program FMaterial::getProgramWithVariants( program.specializationConstants(mSpecializationConstants); + program.pushConstants(ShaderStage::VERTEX, mPushConstants[(uint8_t) ShaderStage::VERTEX]); + program.pushConstants(ShaderStage::FRAGMENT, mPushConstants[(uint8_t) ShaderStage::FRAGMENT]); + program.cacheId(utils::hash::combine(size_t(mCacheId), variant.key)); return program; @@ -928,6 +932,42 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde } } +void FMaterial::processPushConstants(FEngine& engine, MaterialParser const* parser) { + utils::FixedCapacityVector& vertexConstants = + mPushConstants[(uint8_t) ShaderStage::VERTEX]; + utils::FixedCapacityVector& fragmentConstants = + mPushConstants[(uint8_t) ShaderStage::FRAGMENT]; + + CString structVarName; + utils::FixedCapacityVector pushConstants; + parser->getPushConstants(&structVarName, &pushConstants); + + vertexConstants.reserve(pushConstants.size()); + fragmentConstants.reserve(pushConstants.size()); + + constexpr size_t MAX_NAME_LEN = 60; + char buf[MAX_NAME_LEN]; + uint8_t vertexCount = 0, fragmentCount = 0; + + std::for_each(pushConstants.cbegin(), pushConstants.cend(), + [&](MaterialPushConstant const& constant) { + snprintf(buf, sizeof(buf), "%s.%s", structVarName.c_str(), constant.name.c_str()); + + switch (constant.stage) { + case ShaderStage::VERTEX: + vertexConstants.push_back({utils::CString(buf), constant.type}); + vertexCount++; + break; + case ShaderStage::FRAGMENT: + fragmentConstants.push_back({utils::CString(buf), constant.type}); + fragmentCount++; + break; + case ShaderStage::COMPUTE: + break; + } + }); +} + void FMaterial::processDepthVariants(FEngine& engine, MaterialParser const* const parser) { parser->hasCustomDepthShader(&mHasCustomDepthShader); diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index c64686b9f97..617f9699501 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -253,6 +253,8 @@ class FMaterial : public Material { void processSpecializationConstants(FEngine& engine, Material::Builder const& builder, MaterialParser const* parser); + void processPushConstants(FEngine& engine, MaterialParser const* parser); + void processDepthVariants(FEngine& engine, MaterialParser const* parser); void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept; @@ -318,6 +320,11 @@ class FMaterial : public Material { // current specialization constants for the HwProgram utils::FixedCapacityVector mSpecializationConstants; + // current push constants for the HwProgram + std::array, + backend::Program::SHADER_TYPE_COUNT> + mPushConstants; + #if FILAMENT_ENABLE_MATDBG matdbg::MaterialKey mDebuggerId; mutable utils::Mutex mActiveProgramsLock; diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h index c80ac7d8c91..4a4561c14a9 100644 --- a/libs/filabridge/include/filament/MaterialChunkType.h +++ b/libs/filabridge/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/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 30b2f0663c6..a35a68c7932 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -71,6 +72,10 @@ enum class ReservedSpecializationConstants : uint8_t { CONFIG_STEREO_EYE_COUNT = 8, // don't change (hardcoded in ShaderCompilerService.cpp) }; +enum class PushConstantIds { + MORPHING_BUFFER_OFFSET = 0, +}; + // This value is limited by UBO size, ES3.0 only guarantees 16 KiB. // It's also limited by the Froxelizer's record buffer data type (uint8_t). constexpr size_t CONFIG_MAX_LIGHT_COUNT = 256; diff --git a/libs/filabridge/include/private/filament/PushConstantInfo.h b/libs/filabridge/include/private/filament/PushConstantInfo.h new file mode 100644 index 00000000000..45b1615664a --- /dev/null +++ b/libs/filabridge/include/private/filament/PushConstantInfo.h @@ -0,0 +1,43 @@ +/* + * 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_PUSHCONSTANTINFO_H +#define TNT_FILAMENT_PUSHCONSTANTINFO_H + +#include + +#include + +namespace filament { + +struct MaterialPushConstant { + using ShaderStage = backend::ShaderStage; + using ConstantType = backend::ConstantType; + + utils::CString name; + ConstantType type; + ShaderStage stage; + + MaterialPushConstant() = default; + MaterialPushConstant(const char* name, ConstantType type, ShaderStage stage) + : name(name), + type(type), + stage(stage) {} +}; + +} + +#endif // TNT_FILAMENT_PUSHCONSTANTINFO_H diff --git a/libs/filamat/CMakeLists.txt b/libs/filamat/CMakeLists.txt index 4e97538f726..2fc20d7e916 100644 --- a/libs/filamat/CMakeLists.txt +++ b/libs/filamat/CMakeLists.txt @@ -23,7 +23,8 @@ set(COMMON_PRIVATE_HDRS src/eiff/MaterialInterfaceBlockChunk.h src/eiff/ShaderEntry.h src/eiff/SimpleFieldChunk.h - src/Includes.h) + src/Includes.h + src/PushConstantDefinitions.h) set(COMMON_SRCS src/eiff/Chunk.cpp diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index 4b66965d746..c3045b942ba 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/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/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 9acf657353a..ee897754f66 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -20,6 +20,7 @@ #include "Includes.h" #include "MaterialVariants.h" +#include "PushConstantDefinitions.h" #include "shaders/SibGenerator.h" #include "shaders/UibGenerator.h" @@ -173,6 +174,8 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, MaterialBuilder::MaterialBuilder() : mMaterialName("Unnamed") { std::fill_n(mProperties, MATERIAL_PROPERTIES_COUNT, false); mShaderModels.reset(); + + initPushConstants(); } MaterialBuilder::~MaterialBuilder() = default; @@ -660,6 +663,19 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { info.userMaterialHasCustomDepth = false; } +void MaterialBuilder::initPushConstants() noexcept { + mPushConstants.reserve(PUSH_CONSTANTS.size()); + mPushConstants.resize(PUSH_CONSTANTS.size()); + std::transform(PUSH_CONSTANTS.cbegin(), PUSH_CONSTANTS.cend(), mPushConstants.begin(), + [](filament::MaterialPushConstant const& inConstant) -> PushConstant { + return { + .name = inConstant.name, + .type = inConstant.type, + .stage = inConstant.stage, + }; + }); +} + bool MaterialBuilder::findProperties(backend::ShaderStage type, MaterialBuilder::PropertyList& allProperties, CodeGenParams const& semanticCodeGenParams) noexcept { @@ -828,7 +844,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector(std::move(constantsEntry)); + utils::FixedCapacityVector pushConstantsEntry(mPushConstants.size()); + std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(), + [](PushConstant const& c) { + return MaterialPushConstant(c.name.c_str(), c.type, c.stage); + }); + container.push( + utils::CString(PUSH_CONSTANT_STRUCT_VAR_NAME), std::move(pushConstantsEntry)); + // TODO: should we write the SSBO info? this would only be needed if we wanted to provide // an interface to set [get?] values in the buffer. But we can do that easily // with a c-struct (what about kotlin/java?). tbd. diff --git a/libs/filamat/src/PushConstantDefinitions.h b/libs/filamat/src/PushConstantDefinitions.h new file mode 100644 index 00000000000..244326969cd --- /dev/null +++ b/libs/filamat/src/PushConstantDefinitions.h @@ -0,0 +1,44 @@ +/* + * 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_FILAMAT_PUSH_CONSTANT_DEFINTITIONS_H +#define TNT_FILAMAT_PUSH_CONSTANT_DEFINTITIONS_H + +#include +#include + +#include + +#include + +namespace filamat { + +constexpr char PUSH_CONSTANT_STRUCT_VAR_NAME[] = "pushConstants"; + +utils::FixedCapacityVector const PUSH_CONSTANTS = { + { + "morphingBufferOffset", + filament::backend::ConstantType::INT, + filament::backend::ShaderStage::VERTEX, + }, +}; + +// Make sure that the indices defined in filabridge match the actual array indices defined here. +static_assert(static_cast(filament::PushConstantIds::MORPHING_BUFFER_OFFSET) == 0u); + +}// namespace filamat + +#endif diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp index bf16eb233f8..5f92ca7356b 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -104,6 +105,24 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ +MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk( + CString const& structVarName, utils::FixedCapacityVector constants) + : Chunk(ChunkType::MaterialPushConstants), + mStructVarName(structVarName), + mConstants(std::move(constants)) {} + +void MaterialPushConstantParametersChunk::flatten(Flattener& f) { + f.writeString(mStructVarName.c_str()); + f.writeUint64(mConstants.size()); + for (const auto& constant: mConstants) { + f.writeString(constant.name.c_str()); + f.writeUint8(static_cast(constant.type)); + f.writeUint8(static_cast(constant.stage)); + } +} + +// ------------------------------------------------------------------------------------------------ + MaterialUniformBlockBindingsChunk::MaterialUniformBlockBindingsChunk( utils::FixedCapacityVector> list) : Chunk(ChunkType::MaterialUniformBindings), diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h index ba5f325234d..5faee0147a9 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h @@ -31,6 +31,7 @@ class SamplerInterfaceBlock; class BufferInterfaceBlock; struct SubpassInfo; struct MaterialConstant; +struct MaterialPushConstant; } // namespace filament namespace filamat { @@ -88,6 +89,21 @@ class MaterialConstantParametersChunk final : public Chunk { // ------------------------------------------------------------------------------------------------ +class MaterialPushConstantParametersChunk final : public Chunk { +public: + explicit MaterialPushConstantParametersChunk(utils::CString const& structVarName, + utils::FixedCapacityVector constants); + ~MaterialPushConstantParametersChunk() final = default; + +private: + void flatten(Flattener&) final; + + utils::CString mStructVarName; + utils::FixedCapacityVector mConstants; +}; + +// ------------------------------------------------------------------------------------------------ + class MaterialUniformBlockBindingsChunk final : public Chunk { using Container = utils::FixedCapacityVector< std::pair>; diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 13c6bd07686..09a085a4076 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -17,6 +17,7 @@ #include "CodeGenerator.h" #include "MaterialInfo.h" +#include "../PushConstantDefinitions.h" #include "generated/shaders.h" @@ -454,8 +455,8 @@ io::sstream& CodeGenerator::generateVariable(io::sstream& out, ShaderStage stage } io::sstream& CodeGenerator::generateShaderInputs(io::sstream& out, ShaderStage type, - const AttributeBitset& attributes, Interpolation interpolation) const { - + const AttributeBitset& attributes, Interpolation interpolation, + MaterialBuilder::PushConstantList const& pushConstants) const { auto const& attributeDatabase = MaterialBuilder::getAttributeDatabase(); const char* shading = getInterpolationQualifier(interpolation); @@ -479,6 +480,9 @@ io::sstream& CodeGenerator::generateShaderInputs(io::sstream& out, ShaderStage t } out << getTypeName(attribute.type) << " " << attribute.getAttributeName() << ";\n"; }); + + out << "\n"; + generatePushConstants(out, pushConstants, attributes.size()); } out << "\n"; @@ -906,6 +910,41 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst return out; } +utils::io::sstream& CodeGenerator::generatePushConstants(utils::io::sstream& out, + MaterialBuilder::PushConstantList const& pushConstants, size_t const layoutLocation) const { + static constexpr char const* STRUCT_NAME = "Constants"; + + bool const outputSpirv = + mTargetLanguage == TargetLanguage::SPIRV && mTargetApi != TargetApi::OPENGL; + auto const getType = [](ConstantType const& type) { + switch (type) { + case ConstantType::BOOL: + return "bool"; + case ConstantType::INT: + return "int"; + case ConstantType::FLOAT: + return "float"; + } + }; + if (outputSpirv) { + out << "layout(push_constant) uniform " << STRUCT_NAME << " {\n "; + } else { + out << "struct " << STRUCT_NAME << " {\n"; + } + + for (auto const& constant: pushConstants) { + out << getType(constant.type) << " " << constant.name.c_str() << ";\n"; + } + + if (outputSpirv) { + out << "} " << PUSH_CONSTANT_STRUCT_VAR_NAME << ";\n"; + } else { + out << "};\n"; + out << "LAYOUT_LOCATION(" << static_cast(layoutLocation) << ") uniform " << STRUCT_NAME + << " " << PUSH_CONSTANT_STRUCT_VAR_NAME << ";\n"; + } + return out; +} io::sstream& CodeGenerator::generateMaterialProperty(io::sstream& out, MaterialBuilder::Property property, bool isSet) { diff --git a/libs/filamat/src/shaders/CodeGenerator.h b/libs/filamat/src/shaders/CodeGenerator.h index 4467ccc7657..233243f348c 100644 --- a/libs/filamat/src/shaders/CodeGenerator.h +++ b/libs/filamat/src/shaders/CodeGenerator.h @@ -107,7 +107,8 @@ class UTILS_PRIVATE CodeGenerator { // generate declarations for non-custom "in" variables utils::io::sstream& generateShaderInputs(utils::io::sstream& out, ShaderStage type, - const filament::AttributeBitset& attributes, filament::Interpolation interpolation) const; + const filament::AttributeBitset& attributes, filament::Interpolation interpolation, + MaterialBuilder::PushConstantList const& pushConstants) const; static utils::io::sstream& generatePostProcessInputs(utils::io::sstream& out, ShaderStage type); // generate declarations for custom output variables @@ -156,6 +157,10 @@ class UTILS_PRIVATE CodeGenerator { utils::io::sstream& generateSpecializationConstant(utils::io::sstream& out, const char* name, uint32_t id, std::variant value) const; + utils::io::sstream& generatePushConstants(utils::io::sstream& out, + MaterialBuilder::PushConstantList const& pushConstants, + size_t const layoutLocation) const; + static utils::io::sstream& generatePostProcessGetters(utils::io::sstream& out, ShaderStage type); static utils::io::sstream& generateGetters(utils::io::sstream& out, ShaderStage stage); static utils::io::sstream& generateParameters(utils::io::sstream& out, ShaderStage type); diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 92508566857..6ad24c01481 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -304,6 +304,7 @@ ShaderGenerator::ShaderGenerator( MaterialBuilder::OutputList const& outputs, MaterialBuilder::PreprocessorDefineList const& defines, MaterialBuilder::ConstantList const& constants, + MaterialBuilder::PushConstantList const& pushConstants, CString const& materialCode, size_t lineOffset, CString const& materialVertexCode, size_t vertexLineOffset, MaterialBuilder::MaterialDomain materialDomain) noexcept { @@ -325,6 +326,7 @@ ShaderGenerator::ShaderGenerator( mMaterialDomain = materialDomain; mDefines = defines; mConstants = constants; + mPushConstants = pushConstants; if (mMaterialFragmentCode.empty()) { if (mMaterialDomain == MaterialBuilder::MaterialDomain::SURFACE) { @@ -418,7 +420,15 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, attributes.set(VertexAttribute::MORPH_TANGENTS_3); } } - cg.generateShaderInputs(vs, ShaderStage::VERTEX, attributes, interpolation); + + MaterialBuilder::PushConstantList vertexPushConstants; + std::copy_if(mPushConstants.begin(), mPushConstants.end(), + std::back_insert_iterator(vertexPushConstants), + [](MaterialBuilder::PushConstant const& constant) { + return constant.stage == ShaderStage::VERTEX; + }); + cg.generateShaderInputs(vs, ShaderStage::VERTEX, attributes, interpolation, + vertexPushConstants); CodeGenerator::generateCommonTypes(vs, ShaderStage::VERTEX); @@ -519,9 +529,14 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, generateSurfaceMaterialVariantProperties(fs, mProperties, mDefines); - - cg.generateShaderInputs(fs, ShaderStage::FRAGMENT, - material.requiredAttributes, interpolation); + MaterialBuilder::PushConstantList fragmentPushConstants; + std::copy_if(mPushConstants.begin(), mPushConstants.end(), + std::back_insert_iterator(fragmentPushConstants), + [](MaterialBuilder::PushConstant const& constant) { + return constant.stage == ShaderStage::FRAGMENT; + }); + cg.generateShaderInputs(fs, ShaderStage::FRAGMENT, material.requiredAttributes, interpolation, + fragmentPushConstants); CodeGenerator::generateCommonTypes(fs, ShaderStage::FRAGMENT); diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index 59b7f2685ab..1b419f3b06c 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -43,6 +43,7 @@ class ShaderGenerator { MaterialBuilder::OutputList const& outputs, MaterialBuilder::PreprocessorDefineList const& defines, MaterialBuilder::ConstantList const& constants, + MaterialBuilder::PushConstantList const& pushConstants, utils::CString const& materialCode, size_t lineOffset, utils::CString const& materialVertexCode, @@ -125,6 +126,7 @@ class ShaderGenerator { MaterialBuilder::MaterialDomain mMaterialDomain; MaterialBuilder::PreprocessorDefineList mDefines; MaterialBuilder::ConstantList mConstants; + MaterialBuilder::PushConstantList mPushConstants; utils::CString mMaterialFragmentCode; // fragment or compute code utils::CString mMaterialVertexCode; size_t mMaterialLineOffset; From 7f8fbe586c3e4086c730e4f0983225d0147192ae Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Fri, 10 May 2024 10:23:32 -0700 Subject: [PATCH 08/20] gl: push constant small clean-up (#7841) --- filament/backend/src/opengl/OpenGLDriver.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index d136a63c95a..e70cba339a1 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -212,7 +212,8 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi mHandleAllocator("Handles", driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck), - mDriverConfig(driverConfig) { + mDriverConfig(driverConfig), + mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) { std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr); @@ -269,9 +270,8 @@ void OpenGLDriver::terminate() { assert_invariant(mGpuCommandCompleteOps.empty()); #endif - if (mCurrentPushConstants) { - delete mCurrentPushConstants; - } + delete mCurrentPushConstants; + mCurrentPushConstants = nullptr; mContext.terminate(); @@ -295,9 +295,6 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept { void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value) { - assert_invariant(mCurrentPushConstants && - "Calling setPushConstant() before binding a pipeline"); - assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT); utils::Slice> constants; if (stage == ShaderStage::VERTEX) { @@ -3848,12 +3845,7 @@ void OpenGLDriver::bindPipeline(PipelineState state) { gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant); OpenGLProgram* const p = handle_cast(state.program); mValidProgram = useProgram(p); - - if (!mCurrentPushConstants) { - mCurrentPushConstants = new (std::nothrow) PushConstantBundle{p->getPushConstants()}; - } else { - (*mCurrentPushConstants) = p->getPushConstants(); - } + (*mCurrentPushConstants) = p->getPushConstants(); } void OpenGLDriver::bindRenderPrimitive(Handle rph) { From 54a800a25dac26021cb001aa3740f4a0d8a0ffaa Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 10 May 2024 11:14:41 -0700 Subject: [PATCH 09/20] Metal: implement more accurate buffer tracking (#7839) --- filament/backend/src/metal/MetalBuffer.h | 106 +++++++++--------- filament/backend/src/metal/MetalBuffer.mm | 20 ++-- filament/backend/src/metal/MetalBufferPool.h | 2 +- filament/backend/src/metal/MetalBufferPool.mm | 3 +- filament/backend/src/metal/MetalDriver.mm | 22 ++-- filament/backend/src/metal/MetalHandles.mm | 4 +- 6 files changed, 81 insertions(+), 76 deletions(-) diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 081ca6c3fd0..c86e08a8c7a 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -65,9 +65,12 @@ class ScopedAllocationTimer { const char* mName; }; -class TrackedMetalBuffer { -public: +#ifndef FILAMENT_METAL_BUFFER_TRACKING +#define FILAMENT_METAL_BUFFER_TRACKING 0 +#endif +class MetalBufferTracking { +public: static constexpr size_t EXCESS_BUFFER_COUNT = 30000; enum class Type { @@ -91,66 +94,57 @@ class TrackedMetalBuffer { } } - TrackedMetalBuffer() noexcept : mBuffer(nil) {} - TrackedMetalBuffer(nullptr_t) noexcept : mBuffer(nil) {} - TrackedMetalBuffer(id buffer, Type type) : mBuffer(buffer), mType(type) { - assert_invariant(type != Type::NONE); - if (buffer) { - aliveBuffers[toIndex(type)]++; - mType = type; - if (getAliveBuffers() >= EXCESS_BUFFER_COUNT) { - if (platform && platform->hasDebugUpdateStatFunc()) { - platform->debugUpdateStat("filament.metal.excess_buffers_allocated", - TrackedMetalBuffer::getAliveBuffers()); - } +#if FILAMENT_METAL_BUFFER_TRACKING + static void initialize() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + for (size_t i = 0; i < TypeCount; i++) { + aliveBuffers[i] = [NSHashTable weakObjectsHashTable]; } - } + }); } - ~TrackedMetalBuffer() { - if (mBuffer) { - assert_invariant(mType != Type::NONE); - aliveBuffers[toIndex(mType)]--; - } - } - - TrackedMetalBuffer(TrackedMetalBuffer&&) = delete; - TrackedMetalBuffer(TrackedMetalBuffer const&) = delete; - TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete; + static void setPlatform(MetalPlatform* p) { platform = p; } - TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept { - swap(rhs); - return *this; + static void track(id buffer, Type type) { + assert_invariant(type != Type::NONE); + if (UTILS_UNLIKELY(getAliveBuffers() >= EXCESS_BUFFER_COUNT)) { + if (platform && platform->hasDebugUpdateStatFunc()) { + platform->debugUpdateStat("filament.metal.excess_buffers_allocated", + MetalBufferTracking::getAliveBuffers()); + } + } + [aliveBuffers[toIndex(type)] addObject:buffer]; } - id get() const noexcept { return mBuffer; } - operator bool() const noexcept { return bool(mBuffer); } - static uint64_t getAliveBuffers() { uint64_t sum = 0; - for (const auto& v : aliveBuffers) { - sum += v; + for (size_t i = 1; i < TypeCount; i++) { + sum += getAliveBuffers(static_cast(i)); } return sum; } static uint64_t getAliveBuffers(Type type) { assert_invariant(type != Type::NONE); - return aliveBuffers[toIndex(type)]; + NSHashTable* hashTable = aliveBuffers[toIndex(type)]; + // Caution! We can't simply use hashTable.count here, which is inaccurate. + // See http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/ + return hashTable.objectEnumerator.allObjects.count; } - static void setPlatform(MetalPlatform* p) { platform = p; } +#else + static void initialize() {} + static void setPlatform(MetalPlatform* p) {} + static id track(id buffer, Type type) { return buffer; } + static uint64_t getAliveBuffers() { return 0; } + static uint64_t getAliveBuffers(Type type) { return 0; } +#endif private: - void swap(TrackedMetalBuffer& other) noexcept { - std::swap(mBuffer, other.mBuffer); - std::swap(mType, other.mType); - } - - id mBuffer; - Type mType = Type::NONE; - +#if FILAMENT_METAL_BUFFER_TRACKING + static std::array>*, TypeCount> aliveBuffers; static MetalPlatform* platform; - static std::array aliveBuffers; +#endif }; class MetalBuffer { @@ -204,7 +198,7 @@ class MetalBuffer { private: - TrackedMetalBuffer mBuffer; + id mBuffer; size_t mBufferSize = 0; void* mCpuBuffer = nullptr; MetalContext& mContext; @@ -253,9 +247,11 @@ class MetalRingBuffer { mBufferOptions(options), mSlotSizeBytes(computeSlotSize(layout)), mSlotCount(slotCount) { - ScopedAllocationTimer timer("ring"); - mBuffer = { [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions], - TrackedMetalBuffer::Type::RING }; + { + ScopedAllocationTimer timer("ring"); + mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions]; + } + MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::RING); assert_invariant(mBuffer); } @@ -275,11 +271,11 @@ class MetalRingBuffer { // finishes executing. { ScopedAllocationTimer timer("ring"); - mAuxBuffer = { [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions], - TrackedMetalBuffer::Type::RING }; + mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions]; } + MetalBufferTracking::track(mAuxBuffer, MetalBufferTracking::Type::RING); assert_invariant(mAuxBuffer); - return { mAuxBuffer.get(), 0 }; + return { mAuxBuffer, 0 }; } mCurrentSlot = (mCurrentSlot + 1) % mSlotCount; mOccupiedSlots->fetch_add(1, std::memory_order_relaxed); @@ -308,9 +304,9 @@ class MetalRingBuffer { */ std::pair, NSUInteger> getCurrentAllocation() const { if (UTILS_UNLIKELY(mAuxBuffer)) { - return { mAuxBuffer.get(), 0 }; + return { mAuxBuffer, 0 }; } - return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes }; + return { mBuffer, mCurrentSlot * mSlotSizeBytes }; } bool canAccomodateLayout(MTLSizeAndAlign layout) const { @@ -319,8 +315,8 @@ class MetalRingBuffer { private: id mDevice; - TrackedMetalBuffer mBuffer; - TrackedMetalBuffer mAuxBuffer; + id mBuffer; + id mAuxBuffer; MTLResourceOptions mBufferOptions; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index 5f09a290781..32804f43f3c 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -22,10 +22,14 @@ namespace filament { namespace backend { -std::array TrackedMetalBuffer::aliveBuffers = { 0 }; -MetalPlatform* TrackedMetalBuffer::platform = nullptr; MetalPlatform* ScopedAllocationTimer::platform = nullptr; +#if FILAMENT_METAL_BUFFER_TRACKING +std::array>*, MetalBufferTracking::TypeCount> + MetalBufferTracking::aliveBuffers; +MetalPlatform* MetalBufferTracking::platform = nullptr; +#endif + MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage, size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) { // If the buffer is less than 4K in size and is updated frequently, we don't use an explicit @@ -41,9 +45,9 @@ // Otherwise, we allocate a private GPU buffer. { ScopedAllocationTimer timer("generic"); - mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate], - TrackedMetalBuffer::Type::GENERIC }; + mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate]; } + MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::GENERIC); ASSERT_POSTCONDITION(mBuffer, "Could not allocate Metal buffer of size %zu.", size); } @@ -70,7 +74,7 @@ // Acquire a staging buffer to hold the contents of this update. MetalBufferPool* bufferPool = mContext.bufferPool; const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size); - memcpy(staging->buffer.get().contents, src, size); + memcpy(staging->buffer.contents, src, size); // The blit below requires that byteOffset be a multiple of 4. ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4"); @@ -79,9 +83,9 @@ id cmdBuffer = getPendingCommandBuffer(&mContext); id blitEncoder = [cmdBuffer blitCommandEncoder]; blitEncoder.label = @"Buffer upload blit"; - [blitEncoder copyFromBuffer:staging->buffer.get() + [blitEncoder copyFromBuffer:staging->buffer sourceOffset:0 - toBuffer:mBuffer.get() + toBuffer:mBuffer destinationOffset:byteOffset size:size]; [blitEncoder endEncoding]; @@ -102,7 +106,7 @@ return nil; } assert_invariant(mBuffer); - return mBuffer.get(); + return mBuffer; } void MetalBuffer::bindBuffers(id cmdBuffer, id encoder, diff --git a/filament/backend/src/metal/MetalBufferPool.h b/filament/backend/src/metal/MetalBufferPool.h index 03688ab3c43..2aa7e805a0d 100644 --- a/filament/backend/src/metal/MetalBufferPool.h +++ b/filament/backend/src/metal/MetalBufferPool.h @@ -32,7 +32,7 @@ struct MetalContext; // Immutable POD representing a shared CPU-GPU buffer. struct MetalBufferPoolEntry { - TrackedMetalBuffer buffer; + id buffer; size_t capacity; mutable uint64_t lastAccessed; mutable uint32_t referenceCount; diff --git a/filament/backend/src/metal/MetalBufferPool.mm b/filament/backend/src/metal/MetalBufferPool.mm index a1e54a46239..8c640f7d0a9 100644 --- a/filament/backend/src/metal/MetalBufferPool.mm +++ b/filament/backend/src/metal/MetalBufferPool.mm @@ -48,9 +48,10 @@ buffer = [mContext.device newBufferWithLength:numBytes options:MTLResourceStorageModeShared]; } + MetalBufferTracking::track(buffer, MetalBufferTracking::Type::STAGING); ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes); MetalBufferPoolEntry* stage = new MetalBufferPoolEntry { - .buffer = { buffer, TrackedMetalBuffer::Type::STAGING }, + .buffer = buffer, .capacity = numBytes, .lastAccessed = mCurrentFrame, .referenceCount = 1 diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 62cba820401..6ac20797571 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -105,8 +105,9 @@ driverConfig.disableHandleUseAfterFreeCheck) { mContext->driver = this; - TrackedMetalBuffer::setPlatform(platform); ScopedAllocationTimer::setPlatform(platform); + MetalBufferTracking::initialize(); + MetalBufferTracking::setPlatform(platform); mContext->device = mPlatform.createDevice(); assert_invariant(mContext->device); @@ -201,7 +202,7 @@ } MetalDriver::~MetalDriver() noexcept { - TrackedMetalBuffer::setPlatform(nullptr); + MetalBufferTracking::setPlatform(nullptr); ScopedAllocationTimer::setPlatform(nullptr); mContext->device = nil; mContext->emptyTexture = nil; @@ -223,13 +224,16 @@ os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId); #endif if (mPlatform.hasDebugUpdateStatFunc()) { - mPlatform.debugUpdateStat("filament.metal.alive_buffers", TrackedMetalBuffer::getAliveBuffers()); - mPlatform.debugUpdateStat("filament.metal.alive_buffers.generic", - TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::GENERIC)); - mPlatform.debugUpdateStat("filament.metal.alive_buffers.ring", - TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::RING)); - mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging", - TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::STAGING)); +#if FILAMENT_METAL_BUFFER_TRACKING + const uint64_t generic = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::GENERIC); + const uint64_t ring = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::RING); + const uint64_t staging = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::STAGING); + const uint64_t total = generic + ring + staging; + mPlatform.debugUpdateStat("filament.metal.alive_buffers2", total); + mPlatform.debugUpdateStat("filament.metal.alive_buffers2.generic", generic); + mPlatform.debugUpdateStat("filament.metal.alive_buffers2.ring", ring); + mPlatform.debugUpdateStat("filament.metal.alive_buffers2.staging", staging); +#endif } } diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index e8ab879729a..15958ab60ef 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -789,13 +789,13 @@ static void func(void* user) { PixelBufferDescriptor const& data, const PixelBufferShape& shape) { const size_t stagingBufferSize = shape.totalBytes; auto entry = context.bufferPool->acquireBuffer(stagingBufferSize); - memcpy(entry->buffer.get().contents, + memcpy(entry->buffer.contents, static_cast(data.buffer) + shape.sourceOffset, stagingBufferSize); id blitCommandBuffer = getPendingCommandBuffer(&context); id blitCommandEncoder = [blitCommandBuffer blitCommandEncoder]; blitCommandEncoder.label = @"Texture upload buffer blit"; - [blitCommandEncoder copyFromBuffer:entry->buffer.get() + [blitCommandEncoder copyFromBuffer:entry->buffer sourceOffset:0 sourceBytesPerRow:shape.bytesPerRow sourceBytesPerImage:shape.bytesPerSlice From a0472d3c9f1fa75d4df831bad058806bb745e1d6 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Fri, 10 May 2024 11:25:06 -0700 Subject: [PATCH 10/20] Rename Metal log message --- filament/backend/src/metal/MetalDriver.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 6ac20797571..7f2bbc072f8 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -229,10 +229,10 @@ const uint64_t ring = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::RING); const uint64_t staging = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::STAGING); const uint64_t total = generic + ring + staging; - mPlatform.debugUpdateStat("filament.metal.alive_buffers2", total); - mPlatform.debugUpdateStat("filament.metal.alive_buffers2.generic", generic); - mPlatform.debugUpdateStat("filament.metal.alive_buffers2.ring", ring); - mPlatform.debugUpdateStat("filament.metal.alive_buffers2.staging", staging); + mPlatform.debugUpdateStat("filament.metal.alive_buffers", total); + mPlatform.debugUpdateStat("filament.metal.alive_buffers.generic", generic); + mPlatform.debugUpdateStat("filament.metal.alive_buffers.ring", ring); + mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging", staging); #endif } } From ef15a29c0c6aa877d7b8d3cc677a75895ba65e73 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Fri, 10 May 2024 12:34:35 -0700 Subject: [PATCH 11/20] vk: fix missing lib for backend test --- filament/backend/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 0a84de4fbc9..b10a95a36ec 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -478,6 +478,10 @@ if (APPLE) # linker from removing "unused" symbols. target_link_libraries(backend_test_mac PRIVATE -force_load backend_test) set_target_properties(backend_test_mac PROPERTIES FOLDER Tests) + + # This is needed after XCode 15.3 + set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib) endif() endif() From 4f021583f1c721486baaa9291be5943216c244ec Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 10 May 2024 12:01:16 -0700 Subject: [PATCH 12/20] backend tests were broken by a change in TargetBufferInfo - a field was added, which broke the layout of the structure. We fix it by adding constructors which will handle the old and new way of initializing this structure. - one of the test needed a hash update - OpenGLContext wrongly asserted when trying to unbind texture 0 --- .../include/backend/TargetBufferInfo.h | 27 ++++++++++++++++--- filament/backend/src/opengl/OpenGLContext.cpp | 16 ++++++----- filament/backend/src/ostream.cpp | 1 + filament/backend/test/test_FeedbackLoops.cpp | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/filament/backend/include/backend/TargetBufferInfo.h b/filament/backend/include/backend/TargetBufferInfo.h index ce23fc5fd53..c4d284ccce5 100644 --- a/filament/backend/include/backend/TargetBufferInfo.h +++ b/filament/backend/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/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index eea3d87fa61..5021362a1ec 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -894,12 +894,16 @@ void OpenGLContext::unbindTexture( // unbind this texture from all the units it might be bound to // no need unbind the texture from FBOs because we're not tracking that state (and there is // no need to). - UTILS_NOUNROLL - for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) { - if (state.textures.units[unit].id == texture_id) { - // if this texture is bound, it should be at the same target - assert_invariant(state.textures.units[unit].target == target); - unbindTextureUnit(unit); + // Never attempt to unbind texture 0. This could happen with external textures w/ streaming if + // never populated. + if (texture_id) { + UTILS_NOUNROLL + for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) { + if (state.textures.units[unit].id == texture_id) { + // if this texture is bound, it should be at the same target + assert_invariant(state.textures.units[unit].target == target); + unbindTextureUnit(unit); + } } } } diff --git a/filament/backend/src/ostream.cpp b/filament/backend/src/ostream.cpp index 36d93dfbec3..7c32636b354 100644 --- a/filament/backend/src/ostream.cpp +++ b/filament/backend/src/ostream.cpp @@ -410,6 +410,7 @@ io::ostream& operator<<(io::ostream& out, const RasterState& rs) { io::ostream& operator<<(io::ostream& out, const TargetBufferInfo& tbi) { return out << "TargetBufferInfo{" << "handle=" << tbi.handle + << ", baseViewIndex=" << tbi.baseViewIndex << ", level=" << tbi.level << ", layer=" << tbi.layer << "}"; } diff --git a/filament/backend/test/test_FeedbackLoops.cpp b/filament/backend/test/test_FeedbackLoops.cpp index 232127ede57..7babb407459 100644 --- a/filament/backend/test/test_FeedbackLoops.cpp +++ b/filament/backend/test/test_FeedbackLoops.cpp @@ -265,7 +265,7 @@ TEST_F(BackendTest, FeedbackLoops) { for (auto rt : renderTargets) api.destroyRenderTarget(rt); } - const uint32_t expected = 0xe93a4a07; + const uint32_t expected = 0x70695aa1; printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sPixelHashResult, expected); EXPECT_TRUE(sPixelHashResult == expected); } From 6fc16bdcdace36e63549786a9a5dd4005f625edf Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Mon, 13 May 2024 20:53:36 +0000 Subject: [PATCH 13/20] Release Filament 1.51.8 --- README.md | 4 ++-- RELEASE_NOTES.md | 3 +++ android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f571123d1aa..d554705c90c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.51.7' + implementation 'com.google.android.filament:filament-android:1.51.8' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.51.7' +pod 'Filament', '~> 1.51.8' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 90f172e638b..e07e626dad4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.51.9 + + ## v1.51.8 - filagui: Fix regression which broke WebGL diff --git a/android/gradle.properties b/android/gradle.properties index fd04602ea9c..dec17d0ce52 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.51.7 +VERSION_NAME=1.51.8 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index ec121f61e9e..0b049607cba 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.51.7" + spec.version = "1.51.8" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.7/filament-v1.51.7-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.8/filament-v1.51.8-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 9523529679f..bed6c9029f6 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.51.7", + "version": "1.51.8", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From c2e3a97705ca9b89ba7af1ab990893864006d8af Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Mon, 13 May 2024 20:53:51 +0000 Subject: [PATCH 14/20] Bump version to 1.51.9 --- README.md | 4 ++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d554705c90c..4bf3c0ed14a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.51.8' + implementation 'com.google.android.filament:filament-android:1.51.9' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.51.8' +pod 'Filament', '~> 1.51.9' ``` ### Snapshots diff --git a/android/gradle.properties b/android/gradle.properties index dec17d0ce52..013f0d94acd 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.51.8 +VERSION_NAME=1.51.9 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 0b049607cba..ff8bb90d991 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.51.8" + spec.version = "1.51.9" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.8/filament-v1.51.8-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.9/filament-v1.51.9-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index bed6c9029f6..707476b02ec 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.51.8", + "version": "1.51.9", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From a5541de84d85d7d47cfae4c16b73147840089097 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 15 May 2024 10:50:10 -0700 Subject: [PATCH 15/20] Metal, fix callbacks being called only once (#7856) --- filament/backend/CMakeLists.txt | 1 + .../include/private/backend/DriverAPI.inc | 3 +- filament/backend/src/metal/MetalDriver.mm | 6 +- filament/backend/src/metal/MetalHandles.h | 7 +- filament/backend/src/metal/MetalHandles.mm | 39 ++++-- filament/backend/src/noop/NoopDriver.cpp | 2 +- filament/backend/src/opengl/OpenGLDriver.cpp | 2 +- filament/backend/src/vulkan/VulkanDriver.cpp | 2 +- filament/backend/test/test_Callbacks.cpp | 122 ++++++++++++++++++ filament/src/details/SwapChain.cpp | 23 +--- 10 files changed, 163 insertions(+), 44 deletions(-) create mode 100644 filament/backend/test/test_Callbacks.cpp diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index b10a95a36ec..c516cbb8127 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -417,6 +417,7 @@ if (APPLE OR LINUX) test/test_MissingRequiredAttributes.cpp test/test_ReadPixels.cpp test/test_BufferUpdates.cpp + test/test_Callbacks.cpp test/test_MRT.cpp test/test_LoadImage.cpp test/test_StencilBuffer.cpp diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 3e13c4c3547..712386b93c6 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -144,8 +144,7 @@ DECL_DRIVER_API_N(setFrameScheduledCallback, DECL_DRIVER_API_N(setFrameCompletedCallback, backend::SwapChainHandle, sch, backend::CallbackHandler*, handler, - backend::CallbackHandler::Callback, callback, - void*, user) + utils::Invocable&&, callback) DECL_DRIVER_API_N(setPresentationTime, int64_t, monotonic_clock_ns) diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 7f2bbc072f8..07f3768bede 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -243,10 +243,10 @@ swapChain->setFrameScheduledCallback(handler, std::move(callback)); } -void MetalDriver::setFrameCompletedCallback(Handle sch, - CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { +void MetalDriver::setFrameCompletedCallback( + Handle sch, CallbackHandler* handler, utils::Invocable&& callback) { auto* swapChain = handle_cast(sch); - swapChain->setFrameCompletedCallback(handler, callback, user); + swapChain->setFrameCompletedCallback(handler, std::move(callback)); } void MetalDriver::execute(std::function const& fn) noexcept { diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index c6c65e1f7d6..05ce7529d27 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -75,7 +75,7 @@ class MetalSwapChain : public HwSwapChain { void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback); void setFrameCompletedCallback( - CallbackHandler* handler, CallbackHandler::Callback callback, void* user); + CallbackHandler* handler, utils::Invocable&& callback); // For CAMetalLayer-backed SwapChains, presents the drawable or schedules a // FrameScheduledCallback. @@ -119,13 +119,12 @@ class MetalSwapChain : public HwSwapChain { // PresentCallable object. struct { CallbackHandler* handler = nullptr; - FrameScheduledCallback callback = {}; + std::shared_ptr callback = nullptr; } frameScheduled; struct { CallbackHandler* handler = nullptr; - CallbackHandler::Callback callback = {}; - void* user = nullptr; + std::shared_ptr> callback = nullptr; } frameCompleted; }; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 15958ab60ef..2b770241ab0 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -224,14 +224,13 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { void MetalSwapChain::setFrameScheduledCallback( CallbackHandler* handler, FrameScheduledCallback&& callback) { frameScheduled.handler = handler; - frameScheduled.callback = std::move(callback); + frameScheduled.callback = std::make_shared(std::move(callback)); } -void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler, - CallbackHandler::Callback callback, void* user) { +void MetalSwapChain::setFrameCompletedCallback( + CallbackHandler* handler, utils::Invocable&& callback) { frameCompleted.handler = handler; - frameCompleted.callback = callback; - frameCompleted.user = user; + frameCompleted.callback = std::make_shared>(std::move(callback)); } void MetalSwapChain::present() { @@ -304,17 +303,17 @@ void presentDrawable(bool presentFrame, void* user) { assert_invariant(drawable); struct Callback { - Callback(FrameScheduledCallback&& callback, id drawable, + Callback(std::shared_ptr callback, id drawable, MetalDriver* driver) - : f(std::move(callback)), data(PresentDrawableData::create(drawable, driver)) {} - FrameScheduledCallback f; + : f(callback), data(PresentDrawableData::create(drawable, driver)) {} + std::shared_ptr f; // PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later. std::unique_ptr data; static void func(void* user) { auto* const c = reinterpret_cast(user); PresentDrawableData* presentDrawableData = c->data.release(); PresentCallable presentCallable(presentDrawable, presentDrawableData); - c->f(presentCallable); + c->f->operator()(presentCallable); delete c; } }; @@ -322,7 +321,7 @@ static void func(void* user) { // This callback pointer will be captured by the block. Even if the scheduled handler is never // called, the unique_ptr will still ensure we don't leak memory. __block auto callback = - std::make_unique(std::move(frameScheduled.callback), drawable, context.driver); + std::make_unique(frameScheduled.callback, drawable, context.driver); backend::CallbackHandler* handler = frameScheduled.handler; MetalDriver* driver = context.driver; @@ -337,13 +336,25 @@ static void func(void* user) { return; } - CallbackHandler* handler = frameCompleted.handler; - void* user = frameCompleted.user; - CallbackHandler::Callback callback = frameCompleted.callback; + struct Callback { + Callback(std::shared_ptr> callback) : f(callback) {} + std::shared_ptr> f; + static void func(void* user) { + auto* const c = reinterpret_cast(user); + c->f->operator()(); + delete c; + } + }; + + // This callback pointer will be captured by the block. Even if the completed handler is never + // called, the unique_ptr will still ensure we don't leak memory. + __block auto callback = std::make_unique(frameCompleted.callback); + CallbackHandler* handler = frameCompleted.handler; MetalDriver* driver = context.driver; [getPendingCommandBuffer(&context) addCompletedHandler:^(id cb) { - driver->scheduleCallback(handler, user, callback); + Callback* user = callback.release(); + driver->scheduleCallback(handler, user, &Callback::func); }]; } diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 9b3b21f168a..b21a7796ae8 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -59,7 +59,7 @@ void NoopDriver::setFrameScheduledCallback(Handle sch, } void NoopDriver::setFrameCompletedCallback(Handle sch, - CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { + CallbackHandler* handler, utils::Invocable&& callback) { } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index e70cba339a1..d22f4b23f5e 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -3459,7 +3459,7 @@ void OpenGLDriver::setFrameScheduledCallback(Handle sch, } void OpenGLDriver::setFrameCompletedCallback(Handle sch, - CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { + CallbackHandler* handler, utils::Invocable&& callback) { DEBUG_MARKER() } diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 1cdc8a20cc0..4bedc8566f4 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -398,7 +398,7 @@ void VulkanDriver::setFrameScheduledCallback(Handle sch, } void VulkanDriver::setFrameCompletedCallback(Handle sch, - CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { + CallbackHandler* handler, utils::Invocable&& callback) { } void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) { diff --git a/filament/backend/test/test_Callbacks.cpp b/filament/backend/test/test_Callbacks.cpp new file mode 100644 index 00000000000..0f02f1b2141 --- /dev/null +++ b/filament/backend/test/test_Callbacks.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#include "BackendTest.h" + +using namespace filament; +using namespace filament::backend; + +namespace test { + +TEST_F(BackendTest, FrameScheduledCallback) { + auto& api = getDriverApi(); + + // Create a SwapChain. + // In order for the frameScheduledCallback to be called, this must be a real SwapChain (not + // headless) so we obtain a drawable. + auto swapChain = createSwapChain(); + + Handle renderTarget = api.createDefaultRenderTarget(); + + int callbackCountA = 0; + api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) { + callable(); + callbackCountA++; + }); + + // Render the first frame. + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + api.beginRenderPass(renderTarget, {}); + api.endRenderPass(0); + api.commit(swapChain); + api.endFrame(0); + + // Render the next frame. The same callback should be called. + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + api.beginRenderPass(renderTarget, {}); + api.endRenderPass(0); + api.commit(swapChain); + api.endFrame(0); + + // Now switch out the callback. + int callbackCountB = 0; + api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) { + callable(); + callbackCountB++; + }); + + // Render one final frame. + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + api.beginRenderPass(renderTarget, {}); + api.endRenderPass(0); + api.commit(swapChain); + api.endFrame(0); + + api.finish(); + + executeCommands(); + getDriver().purge(); + + EXPECT_EQ(callbackCountA, 2); + EXPECT_EQ(callbackCountB, 1); +} + +TEST_F(BackendTest, FrameCompletedCallback) { + auto& api = getDriverApi(); + + // Create a SwapChain. + auto swapChain = api.createSwapChainHeadless(256, 256, 0); + + int callbackCountA = 0; + api.setFrameCompletedCallback(swapChain, nullptr, + [&callbackCountA]() { callbackCountA++; }); + + // Render the first frame. + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + api.commit(swapChain); + api.endFrame(0); + + // Render the next frame. The same callback should be called. + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + api.commit(swapChain); + api.endFrame(0); + + // Now switch out the callback. + int callbackCountB = 0; + api.setFrameCompletedCallback(swapChain, nullptr, + [&callbackCountB]() { callbackCountB++; }); + + // Render one final frame. + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + api.commit(swapChain); + api.endFrame(0); + + api.finish(); + + executeCommands(); + getDriver().purge(); + + EXPECT_EQ(callbackCountA, 2); + EXPECT_EQ(callbackCountB, 1); +} + +} // namespace test diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp index 0407d893763..20589c38f14 100644 --- a/filament/src/details/SwapChain.cpp +++ b/filament/src/details/SwapChain.cpp @@ -79,24 +79,11 @@ bool FSwapChain::isFrameScheduledCallbackSet() const noexcept { return mFrameScheduledCallbackIsSet; } -void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, - utils::Invocable&& callback) noexcept { - struct Callback { - utils::Invocable f; - SwapChain* s; - static void func(void* user) { - auto* const c = reinterpret_cast(user); - c->f(c->s); - delete c; - } - }; - if (callback) { - auto* const user = new(std::nothrow) Callback{ std::move(callback), this }; - mEngine.getDriverApi().setFrameCompletedCallback( - mHwSwapChain, handler, &Callback::func, static_cast(user)); - } else { - mEngine.getDriverApi().setFrameCompletedCallback(mHwSwapChain, nullptr, nullptr, nullptr); - } +void FSwapChain::setFrameCompletedCallback( + backend::CallbackHandler* handler, FrameCompletedCallback&& callback) noexcept { + using namespace std::placeholders; + auto boundCallback = std::bind(std::move(callback), this); + mEngine.getDriverApi().setFrameCompletedCallback(mHwSwapChain, handler, std::move(boundCallback)); } bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept { From 5485ef238f39f92749f3d3de3bb78e6809fb4374 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Thu, 16 May 2024 13:20:12 -0700 Subject: [PATCH 16/20] Implement push constants for Metal (#7858) --- filament/backend/CMakeLists.txt | 1 + .../backend/include/backend/DriverEnums.h | 2 +- filament/backend/src/metal/MetalContext.h | 14 ++ filament/backend/src/metal/MetalContext.mm | 63 +++++++ filament/backend/src/metal/MetalDriver.mm | 21 ++- filament/backend/src/metal/MetalState.h | 6 +- filament/backend/test/test_PushConstants.cpp | 168 ++++++++++++++++++ libs/filamat/src/GLSLPostProcessor.cpp | 11 ++ 8 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 filament/backend/test/test_PushConstants.cpp diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index c516cbb8127..075815f4ac0 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -419,6 +419,7 @@ if (APPLE OR LINUX) test/test_BufferUpdates.cpp test/test_Callbacks.cpp test/test_MRT.cpp + test/test_PushConstants.cpp test/test_LoadImage.cpp test/test_StencilBuffer.cpp test/test_Scissor.cpp diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 490b889fa2e..aeae5e2e9b9 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -117,7 +117,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. /** diff --git a/filament/backend/src/metal/MetalContext.h b/filament/backend/src/metal/MetalContext.h index 33b8f92c829..8a14d9a36c8 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -55,6 +55,18 @@ struct MetalVertexBuffer; constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples +class MetalPushConstantBuffer { +public: + void setPushConstant(PushConstantVariant value, uint8_t index); + bool isDirty() const { return mDirty; } + void setBytes(id encoder, ShaderStage stage); + void clear(); + +private: + std::vector mPushConstants; + bool mDirty = false; +}; + struct MetalContext { explicit MetalContext(size_t metalFreedTextureListSize) : texturesToDestroy(metalFreedTextureListSize) {} @@ -109,6 +121,8 @@ struct MetalContext { PolygonOffset currentPolygonOffset = {0.0f, 0.0f}; + std::array currentPushConstants; + MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {}; // Keeps track of sampler groups we've finalized for the current render pass. diff --git a/filament/backend/src/metal/MetalContext.mm b/filament/backend/src/metal/MetalContext.mm index fce98eb5237..3997944521c 100644 --- a/filament/backend/src/metal/MetalContext.mm +++ b/filament/backend/src/metal/MetalContext.mm @@ -153,5 +153,68 @@ bool isInRenderPass(MetalContext* context) { return context->currentRenderPassEncoder != nil; } +void MetalPushConstantBuffer::setPushConstant(PushConstantVariant value, uint8_t index) { + if (mPushConstants.size() <= index) { + mPushConstants.resize(index + 1); + mDirty = true; + } + if (UTILS_LIKELY(mPushConstants[index] != value)) { + mDirty = true; + mPushConstants[index] = value; + } +} + +void MetalPushConstantBuffer::setBytes(id encoder, ShaderStage stage) { + constexpr size_t PUSH_CONSTANT_SIZE_BYTES = 4; + constexpr size_t PUSH_CONSTANT_BUFFER_INDEX = 26; + + static char buffer[MAX_PUSH_CONSTANT_COUNT * PUSH_CONSTANT_SIZE_BYTES]; + assert_invariant(mPushConstants.size() <= MAX_PUSH_CONSTANT_COUNT); + + size_t bufferSize = PUSH_CONSTANT_SIZE_BYTES * mPushConstants.size(); + for (size_t i = 0; i < mPushConstants.size(); i++) { + const auto& constant = mPushConstants[i]; + std::visit( + [i](auto arg) { + if constexpr (std::is_same_v) { + // bool push constants are converted to uints in MSL. + // We must ensure we write all the bytes for boolean values to work + // correctly. + uint32_t boolAsUint = arg ? 0x00000001 : 0x00000000; + *(reinterpret_cast(buffer + PUSH_CONSTANT_SIZE_BYTES * i)) = + boolAsUint; + } else { + *(decltype(arg)*)(buffer + PUSH_CONSTANT_SIZE_BYTES * i) = arg; + } + }, + constant); + } + + switch (stage) { + case ShaderStage::VERTEX: + [(id)encoder setVertexBytes:buffer + length:bufferSize + atIndex:PUSH_CONSTANT_BUFFER_INDEX]; + break; + case ShaderStage::FRAGMENT: + [(id)encoder setFragmentBytes:buffer + length:bufferSize + atIndex:PUSH_CONSTANT_BUFFER_INDEX]; + break; + case ShaderStage::COMPUTE: + [(id)encoder setBytes:buffer + length:bufferSize + atIndex:PUSH_CONSTANT_BUFFER_INDEX]; + break; + } + + mDirty = false; +} + +void MetalPushConstantBuffer::clear() { + mPushConstants.clear(); + mDirty = false; +} + } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 07f3768bede..f438da44280 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -1109,6 +1109,10 @@ mContext->currentPolygonOffset = {0.0f, 0.0f}; mContext->finalizedSamplerGroups.clear(); + + for (auto& pc : mContext->currentPushConstants) { + pc.clear(); + } } void MetalDriver::nextSubpass(int dummy) {} @@ -1257,7 +1261,14 @@ } void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, - backend::PushConstantVariant value) {} + backend::PushConstantVariant value) { + ASSERT_PRECONDITION( + isInRenderPass(mContext), "setPushConstant must be called inside a render pass."); + assert_invariant(static_cast(stage) < mContext->currentPushConstants.size()); + MetalPushConstantBuffer& pushConstants = + mContext->currentPushConstants[static_cast(stage)]; + pushConstants.setPushConstant(value, index); +} void MetalDriver::insertEventMarker(const char* string, uint32_t len) { @@ -1865,6 +1876,14 @@ UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT, uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT); + // Update push constants. + for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) { + auto& pushConstants = mContext->currentPushConstants[i]; + if (UTILS_UNLIKELY(pushConstants.isDirty())) { + pushConstants.setBytes(mContext->currentRenderPassEncoder, static_cast(i)); + } + } + auto primitive = handle_cast(mContext->currentRenderPrimitive); MetalIndexBuffer* indexBuffer = primitive->indexBuffer; diff --git a/filament/backend/src/metal/MetalState.h b/filament/backend/src/metal/MetalState.h index 82de98d6961..83579cb5d50 100644 --- a/filament/backend/src/metal/MetalState.h +++ b/filament/backend/src/metal/MetalState.h @@ -43,7 +43,8 @@ inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) { // ------------------------------------------------------ // 0 Zero buffer (placeholder vertex buffer) 1 // 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT -// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT +// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT +// 26 Push constants 1 // 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT // // Total 31 @@ -53,7 +54,8 @@ inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) { // Bindings Buffer name Count // ------------------------------------------------------ // 0-3 SSBO buffers 4 MAX_SSBO_COUNT -// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT +// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT +// 26 Push constants 1 // 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT // // Total 18 diff --git a/filament/backend/test/test_PushConstants.cpp b/filament/backend/test/test_PushConstants.cpp new file mode 100644 index 00000000000..80253fdf07e --- /dev/null +++ b/filament/backend/test/test_PushConstants.cpp @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#include "BackendTest.h" + +#include "ShaderGenerator.h" +#include "TrianglePrimitive.h" + +#include + +namespace test { + +using namespace filament; +using namespace filament::backend; + +static constexpr struct { + size_t TRIANGLE_HIDE = 0; + size_t TRIANGLE_SCALE = 1; + size_t TRIANGLE_OFFSET_X = 2; + size_t TRIANGLE_OFFSET_Y = 3; + + size_t RED = 0; + size_t GREEN = 2; + size_t BLUE = 3; +} pushConstantIndex; + +static const char* const triangleVs = R"(#version 450 core + +layout(push_constant) uniform Constants { + bool hideTriangle; + float triangleScale; + float triangleOffsetX; + float triangleOffsetY; +} pushConstants; + +layout(location = 0) in vec4 mesh_position; +void main() { + if (pushConstants.hideTriangle) { + // Test that bools are written correctly. All bits must be 0 if the bool is false. + gl_Position = vec4(0.0); + return; + } + gl_Position = vec4(mesh_position.xy * pushConstants.triangleScale + + vec2(pushConstants.triangleOffsetX, pushConstants.triangleOffsetY), 0.0, 1.0); +#if defined(TARGET_VULKAN_ENVIRONMENT) + // In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up. + gl_Position.y = -gl_Position.y; +#endif +})"; + +static const char* const triangleFs = R"(#version 450 core + +layout(push_constant) uniform Constants { + float red; + bool padding; // test correct bool padding + float green; + float blue; +} pushConstants; + +precision mediump int; precision highp float; +layout(location = 0) out vec4 fragColor; +void main() { + fragColor = vec4(pushConstants.red, pushConstants.green, pushConstants.blue, 1.0); +})"; + +TEST_F(BackendTest, PushConstants) { + auto& api = getDriverApi(); + + api.startCapture(0); + + // The test is executed within this block scope to force destructors to run before + // executeCommands(). + { + // Create a SwapChain and make it current. + auto swapChain = createSwapChain(); + api.makeCurrent(swapChain, swapChain); + + // Create a program. + ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform); + Program p = shaderGen.getProgram(api); + ProgramHandle program = api.createProgram(std::move(p)); + + Handle renderTarget = api.createDefaultRenderTarget(); + + TrianglePrimitive triangle(api); + + RenderPassParams params = {}; + params.flags.clear = TargetBufferFlags::COLOR0; + params.viewport = { 0, 0, 512, 512 }; + params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f); + params.flags.discardStart = TargetBufferFlags::ALL; + params.flags.discardEnd = TargetBufferFlags::NONE; + + PipelineState ps = {}; + ps.program = program; + ps.rasterState.colorWrite = true; + ps.rasterState.depthWrite = false; + + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + + api.beginRenderPass(renderTarget, params); + + // Set the push constants to scale the triangle in half + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_HIDE, false); + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_SCALE, 0.5f); + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_X, 0.0f); + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_Y, 0.0f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.RED, 0.25f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.GREEN, 0.5f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.BLUE, 1.0f); + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); + + // Draw another triangle, transposed to the upper-right. + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_X, 0.5f); + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_Y, 0.5f); + + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.RED, 1.00f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.GREEN, 0.5f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.BLUE, 0.25f); + + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); + + // Draw a final triangle, transposed to the lower-left. + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_X, -0.5f); + api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_Y, -0.5f); + + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.RED, 0.5f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.GREEN, 0.25f); + api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.BLUE, 1.00f); + + api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1); + + api.endRenderPass(); + + readPixelsAndAssertHash("pushConstants", 512, 512, renderTarget, 1957275826, true); + + api.commit(swapChain); + api.endFrame(0); + + // Cleanup. + api.destroySwapChain(swapChain); + api.destroyRenderTarget(renderTarget); + } + + api.stopCapture(0); + + // Wait for the ReadPixels result to come back. + api.finish(); + + executeCommands(); + getDriver().purge(); +} + +} // namespace test diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 6765b122f51..ea636a57367 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -255,6 +255,17 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, mslCompiler.add_msl_resource_binding(argBufferBinding); } + // Bind push constants to [buffer(26)] + MSLResourceBinding pushConstantBinding; + // the baseType doesn't matter, but can't be UNKNOWN + pushConstantBinding.basetype = SPIRType::BaseType::Struct; + pushConstantBinding.stage = executionModel; + pushConstantBinding.desc_set = kPushConstDescSet; + pushConstantBinding.binding = kPushConstBinding; + pushConstantBinding.count = 1; + pushConstantBinding.msl_buffer = 26; + mslCompiler.add_msl_resource_binding(pushConstantBinding); + auto updateResourceBindingDefault = [executionModel, &mslCompiler](const auto& resource) { auto set = mslCompiler.get_decoration(resource.id, spv::DecorationDescriptorSet); auto binding = mslCompiler.get_decoration(resource.id, spv::DecorationBinding); From 3fa4aab02a4ca550da03f92754e035093688ce48 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 16 May 2024 14:11:07 -0700 Subject: [PATCH 17/20] change the morphing API so it uses only one buffer per renderable The current API allowed to have a buffer for each primitive in a renderable. We instead restrict the API so that there is a single MorphTargetBuffer for the whole renderable, shared by all primitives. The buffer can be shared thanks to the "offset" parameter on setMorphTargetBufferAt(). Also - fix FMorphTargetBuffer::updateDataAt() - add support for the "offset" parameter of setMorphTargetBufferAt() --- .../src/main/cpp/RenderableManager.cpp | 27 +++++-- .../android/filament/RenderableManager.java | 59 ++++++++++---- filament/include/filament/RenderableManager.h | 78 +++++++++++-------- filament/src/RenderPass.cpp | 4 + filament/src/RenderPass.h | 3 +- filament/src/RenderableManager.cpp | 5 ++ filament/src/components/RenderableManager.cpp | 32 +++++++- filament/src/components/RenderableManager.h | 2 + filament/src/details/MorphTargetBuffer.cpp | 10 +-- .../include/private/filament/EngineEnums.h | 4 +- samples/hellomorphing.cpp | 25 +++--- shaders/src/getters.vs | 6 +- 12 files changed, 174 insertions(+), 81 deletions(-) diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index 4e586179baa..db0255d414e 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -244,13 +244,25 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphing(JNIEnv*, jcl builder->morphing(targetCount); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIEnv*, jclass, + jlong nativeBuilder, jlong nativeMorphTargetBuffer) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer; + builder->morphing(morphTargetBuffer); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass, jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer, int offset, int count) { RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; - MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer; - builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count); + if (nativeMorphTargetBuffer) { + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer; + builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count); + } else { + builder->morphing(level, primitiveIndex, offset, count); + } } extern "C" JNIEXPORT void JNICALL @@ -326,9 +338,14 @@ Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEn jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex, jlong nativeMorphTargetBuffer, jint offset, jint count) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; - MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer; - rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level, - (size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count); + if (nativeMorphTargetBuffer) { + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer; + rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level, + (size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count); + } else { + rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level, + (size_t) primitiveIndex, (size_t) offset, (size_t) count); + } } extern "C" JNIEXPORT jint JNICALL diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java index f7a6319d7a8..dfce1af4a16 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java @@ -524,14 +524,7 @@ public Builder skinning(@IntRange(from = 0, to = 255) int boneCount, @NonNull Bu } /** - * 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 {@link MorphTargetBuffer} must be created and provided via - * {@link RenderableManager#setMorphTargetBufferAt}. Standard morphing supports up to - * CONFIG_MAX_MORPH_TARGET_COUNT morph targets.

+ * Controls if the renderable has legacy vertex morphing targets, zero by default. * * For legacy morphing, the attached {@link VertexBuffer} must provide data in the * appropriate {@link VertexBuffer.VertexAttribute} slots (MORPH_POSITION_0 etc). @@ -549,6 +542,22 @@ public Builder morphing(@IntRange(from = 0, to = 255) int targetCount) { return this; } + /** + * Controls if the renderable has vertex morphing targets, zero by default. + * + *

For standard morphing, A {@link MorphTargetBuffer} must be provided. + * Standard morphing supports up to + * CONFIG_MAX_MORPH_TARGET_COUNT morph targets.

+ * + *

See also {@link RenderableManager#setMorphWeights}, which can be called on a per-frame basis + * to advance the animation.

+ */ + @NonNull + public Builder morphing(@NonNull MorphTargetBuffer morphTargetBuffer) { + nBuilderMorphingStandard(mNativeBuilder, morphTargetBuffer.getNativeObject()); + return this; + } + /** * Specifies the morph target buffer for a primitive. * @@ -565,6 +574,17 @@ public Builder morphing(@IntRange(from = 0, to = 255) int targetCount) { * @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) */ @NonNull + public Builder morphing(@IntRange(from = 0) int level, + @IntRange(from = 0) int primitiveIndex, + @IntRange(from = 0) int offset, + @IntRange(from = 0) int count) { + nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, 0, offset, count); + return this; + } + + /** @deprecated */ + @Deprecated + @NonNull public Builder morphing(@IntRange(from = 0) int level, @IntRange(from = 0) int primitiveIndex, @NonNull MorphTargetBuffer morphTargetBuffer, @@ -575,10 +595,8 @@ public Builder morphing(@IntRange(from = 0) int level, return this; } - /** - * Utility method to specify morph target buffer for a primitive. - * For details, see the {@link RenderableManager.Builder#morphing}. - */ + /** @deprecated */ + @Deprecated @NonNull public Builder morphing(@IntRange(from = 0) int level, @IntRange(from = 0) int primitiveIndex, @@ -687,6 +705,16 @@ public void setMorphWeights(@EntityInstance int i, @NonNull float[] weights, @In * * @see Builder#morphing */ + public void setMorphTargetBufferAt(@EntityInstance int i, + @IntRange(from = 0) int level, + @IntRange(from = 0) int primitiveIndex, + @IntRange(from = 0) int offset, + @IntRange(from = 0) int count) { + nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, 0, offset, count); + } + + /** @deprecated */ + @Deprecated public void setMorphTargetBufferAt(@EntityInstance int i, @IntRange(from = 0) int level, @IntRange(from = 0) int primitiveIndex, @@ -697,10 +725,8 @@ public void setMorphTargetBufferAt(@EntityInstance int i, morphTargetBuffer.getNativeObject(), offset, count); } - /** - * Utility method to change morph target buffer for the given primitive. - * For details, see the {@link RenderableManager#setMorphTargetBufferAt}. - */ + /** @deprecated */ + @Deprecated public void setMorphTargetBufferAt(@EntityInstance int i, @IntRange(from = 0) int level, @IntRange(from = 0) int primitiveIndex, @@ -1006,6 +1032,7 @@ public long getNativeObject() { private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining); private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset); private static native void nBuilderMorphing(long nativeBuilder, int targetCount); + private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer); private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count); private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled); private static native void nBuilderFog(long nativeBuilder, boolean enabled); diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index bb50b7d1db8..363ef2d7c1f 100644 --- a/filament/include/filament/RenderableManager.h +++ b/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/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 4c949a26bf4..e13b5da3055 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -684,6 +684,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla cmd.info.indexCount = primitive.getIndexCount(); cmd.info.type = primitive.getPrimitiveType(); cmd.info.morphTargetBuffer = morphTargets.buffer->getHwHandle(); + cmd.info.morphingOffset = morphTargets.offset; if constexpr (isColorPass) { RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces); @@ -1029,6 +1030,9 @@ void RenderPass::Executor::execute(FEngine& engine, rebindPipeline = false; currentPipeline = pipeline; driver.bindPipeline(pipeline); + + driver.setPushConstant(ShaderStage::VERTEX, + +PushConstantIds::MORPHING_BUFFER_OFFSET, int32_t(info.morphingOffset)); } if (info.rph != currentPrimitiveHandle) { diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index b8810de33b7..e1c7b3c1813 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -251,6 +251,7 @@ class RenderPass { uint32_t indexCount; // 4 bytes uint32_t index = 0; // 4 bytes backend::SamplerGroupHandle morphTargetBuffer; // 4 bytes + uint32_t morphingOffset = 0; // 4 bytes backend::RasterState rasterState; // 4 bytes @@ -261,7 +262,7 @@ class RenderPass { bool hasMorphing : 1; // 1 bit bool hasHybridInstancing : 1; // 1 bit - uint32_t rfu[3]; // 16 bytes + uint32_t rfu[2]; // 16 bytes }; static_assert(sizeof(PrimitiveInfo) == 56); diff --git a/filament/src/RenderableManager.cpp b/filament/src/RenderableManager.cpp index 133dd817c2c..9550197acf0 100644 --- a/filament/src/RenderableManager.cpp +++ b/filament/src/RenderableManager.cpp @@ -164,6 +164,11 @@ void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, downcast(morphTargetBuffer), offset, count); } +void RenderableManager::setMorphTargetBufferAt( + Instance instance, uint8_t level, size_t primitiveIndex, size_t offset, size_t count) { + downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, offset, count); +} + MorphTargetBuffer* RenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept { return downcast(this)->getMorphTargetBufferAt(instance, level, primitiveIndex); diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 2cd031059b3..c0907ee880f 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -86,6 +86,7 @@ struct RenderableManager::BuilderDetails { RenderableManager::Builder::GeometryType mGeometryType : 2; size_t mSkinningBoneCount = 0; size_t mMorphTargetCount = 0; + FMorphTargetBuffer* mMorphTargetBuffer = nullptr; Bone const* mUserBones = nullptr; mat4f const* mUserBoneMatrices = nullptr; FSkinningBuffer* mSkinningBuffer = nullptr; @@ -273,6 +274,18 @@ RenderableManager::Builder& RenderableManager::Builder::morphing(size_t targetCo return *this; } +RenderableManager::Builder& RenderableManager::Builder::morphing( + MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept { + mImpl->mMorphTargetBuffer = downcast(morphTargetBuffer); + mImpl->mMorphTargetCount = morphTargetBuffer->getCount(); + return *this; +} + +RenderableManager::Builder& RenderableManager::Builder::morphing( + uint8_t level, size_t primitiveIndex, size_t offset, size_t count) noexcept { + return morphing(level, primitiveIndex, mImpl->mMorphTargetBuffer, offset, count); +} + RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t, size_t primitiveIndex, MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept { std::vector& entries = mImpl->mEntries; @@ -646,10 +659,14 @@ void FRenderableManager::create( // Create and initialize all needed MorphTargets. // It's required to avoid branches in hot loops. + FMorphTargetBuffer* morphTargetBuffer = builder->mMorphTargetBuffer; + if (morphTargetBuffer == nullptr) { + morphTargetBuffer = mEngine.getDummyMorphTargetBuffer(); + } MorphTargets* const morphTargets = new MorphTargets[entryCount]; std::generate_n(morphTargets, entryCount, - [dummy = mEngine.getDummyMorphTargetBuffer()]() -> MorphTargets { - return { dummy, 0, 0 }; + [morphTargetBuffer]() -> MorphTargets { + return { morphTargetBuffer, 0, 0 }; }); mManager[ci].morphTargets = { morphTargets, size_type(entryCount) }; @@ -967,6 +984,17 @@ void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level } } +void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, + size_t primitiveIndex, size_t offset, size_t count) { + if (instance) { + Slice& morphTargets = getMorphTargets(instance, level); + if (primitiveIndex < morphTargets.size()) { + setMorphTargetBufferAt(instance, level, + primitiveIndex, morphTargets[primitiveIndex].buffer, offset, count); + } + } +} + MorphTargetBuffer* FRenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept { if (instance) { diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 70332c63bbf..6135ba2d733 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -157,6 +157,8 @@ class FRenderableManager : public RenderableManager { void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset); void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count); + void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + size_t offset, size_t count); MorphTargetBuffer* getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept; size_t getMorphTargetCount(Instance instance) const noexcept; diff --git a/filament/src/details/MorphTargetBuffer.cpp b/filament/src/details/MorphTargetBuffer.cpp index b615e6e7054..c2f3d9a1384 100644 --- a/filament/src/details/MorphTargetBuffer.cpp +++ b/filament/src/details/MorphTargetBuffer.cpp @@ -153,7 +153,7 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, "MorphTargetBuffer (size=%lu) overflow (count=%u, offset=%u)", (unsigned)mVertexCount, (unsigned)count, (unsigned)offset); - auto size = getSize(mVertexCount); + auto size = getSize(count); ASSERT_PRECONDITION(targetIndex < mCount, "%d target index must be < %d", targetIndex, mCount); @@ -177,7 +177,7 @@ void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, "MorphTargetBuffer (size=%lu) overflow (count=%u, offset=%u)", (unsigned)mVertexCount, (unsigned)count, (unsigned)offset); - auto size = getSize(mVertexCount); + auto size = getSize(count); ASSERT_PRECONDITION(targetIndex < mCount, "%d target index must be < %d", targetIndex, mCount); @@ -200,7 +200,7 @@ void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t targetIndex, "MorphTargetBuffer (size=%lu) overflow (count=%u, offset=%u)", (unsigned)mVertexCount, (unsigned)count, (unsigned)offset); - const auto size = getSize(mVertexCount); + const auto size = getSize(count); ASSERT_PRECONDITION(targetIndex < mCount, "%d target index must be < %d", targetIndex, mCount); @@ -226,8 +226,8 @@ void FMorphTargetBuffer::updateDataAt(backend::DriverApi& driver, size_t const xoffset = offset % MAX_MORPH_TARGET_BUFFER_WIDTH; size_t const textureWidth = getWidth(mVertexCount); size_t const alignment = ((textureWidth - xoffset) % textureWidth); - size_t const lineCount = (count - alignment) / textureWidth; - size_t const lastLineCount = (count - alignment) % textureWidth; + size_t const lineCount = (count > alignment) ? (count - alignment) / textureWidth : 0; + size_t const lastLineCount = (count > alignment) ? (count - alignment) % textureWidth : 0; // 'out' buffer is going to be used up to 3 times, so for simplicity we use a shared_buffer // to manage its lifetime. One side effect of this is that the callbacks below will allocate diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index a35a68c7932..4a8c042e756 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -72,7 +72,7 @@ enum class ReservedSpecializationConstants : uint8_t { CONFIG_STEREO_EYE_COUNT = 8, // don't change (hardcoded in ShaderCompilerService.cpp) }; -enum class PushConstantIds { +enum class PushConstantIds : uint8_t { MORPHING_BUFFER_OFFSET = 0, }; @@ -143,6 +143,8 @@ struct utils::EnableIntegerOperators : public st template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> +struct utils::EnableIntegerOperators : public std::true_type {}; +template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> diff --git a/samples/hellomorphing.cpp b/samples/hellomorphing.cpp index af5124bf4b8..6bd418cf43c 100644 --- a/samples/hellomorphing.cpp +++ b/samples/hellomorphing.cpp @@ -54,7 +54,6 @@ struct App { Skybox* skybox; Entity renderable; MorphTargetBuffer *mt1; - MorphTargetBuffer *mt2; }; struct Vertex { @@ -174,12 +173,7 @@ int main(int argc, char** argv) { .build(*engine); app.mt1 = MorphTargetBuffer::Builder() - .vertexCount(9) - .count(3) - .build(*engine); - - app.mt2 = MorphTargetBuffer::Builder() - .vertexCount(9) + .vertexCount(9 * 2) .count(3) .build(*engine); @@ -190,12 +184,12 @@ int main(int argc, char** argv) { app.mt1->setTangentsAt(*engine,1, targets_tan+3, 3, 0); app.mt1->setTangentsAt(*engine,2, targets_tan+6, 3, 0); - app.mt2->setPositionsAt(*engine,0, targets_pos2, 3, 0); - app.mt2->setPositionsAt(*engine,1, targets_pos2+3, 3, 0); - app.mt2->setPositionsAt(*engine,2, targets_pos2+6, 3, 0); - app.mt2->setTangentsAt(*engine,0, targets_tan, 3, 0); - app.mt2->setTangentsAt(*engine,1, targets_tan+3, 3, 0); - app.mt2->setTangentsAt(*engine,2, targets_tan+6, 3, 0); + app.mt1->setPositionsAt(*engine,0, targets_pos2, 3, 9); + app.mt1->setPositionsAt(*engine,1, targets_pos2+3, 3, 9); + app.mt1->setPositionsAt(*engine,2, targets_pos2+6, 3, 9); + app.mt1->setTangentsAt(*engine,0, targets_tan, 3, 9); + app.mt1->setTangentsAt(*engine,1, targets_tan+3, 3, 9); + app.mt1->setTangentsAt(*engine,2, targets_tan+6, 3, 9); app.renderable = EntityManager::get().create(); @@ -209,8 +203,8 @@ int main(int argc, char** argv) { .receiveShadows(false) .castShadows(false) .morphing(3) - .morphing(0,0,app.mt1) - .morphing(0,1,app.mt2) + .morphing(0,0,app.mt1, 0, app.mt1->getCount()) + .morphing(0,1,app.mt1, 9, app.mt1->getCount()) .build(*engine, app.renderable); scene->addEntity(app.renderable); @@ -226,7 +220,6 @@ int main(int argc, char** argv) { engine->destroy(app.vb); engine->destroy(app.ib); engine->destroy(app.mt1); - engine->destroy(app.mt2); engine->destroyCameraComponent(app.camera); utils::EntityManager::get().destroy(app.camera); }; diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index 4b5efda574f..cccc08319c7 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -146,7 +146,8 @@ void skinNormalTangent(inout vec3 n, inout vec3 t, const uvec4 ids, const vec4 w #define MAX_MORPH_TARGET_BUFFER_WIDTH 2048 void morphPosition(inout vec4 p) { - ivec3 texcoord = ivec3(getVertexIndex() % MAX_MORPH_TARGET_BUFFER_WIDTH, getVertexIndex() / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + int index = getVertexIndex() + pushConstants.morphingBufferOffset; + ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); int c = object_uniforms_morphTargetCount; for (int i = 0; i < c; ++i) { float w = morphingUniforms.weights[i][0]; @@ -159,7 +160,8 @@ void morphPosition(inout vec4 p) { void morphNormal(inout vec3 n) { vec3 baseNormal = n; - ivec3 texcoord = ivec3(getVertexIndex() % MAX_MORPH_TARGET_BUFFER_WIDTH, getVertexIndex() / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + int index = getVertexIndex() + pushConstants.morphingBufferOffset; + ivec3 texcoord = ivec3(index % MAX_MORPH_TARGET_BUFFER_WIDTH, index / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); int c = object_uniforms_morphTargetCount; for (int i = 0; i < c; ++i) { float w = morphingUniforms.weights[i][0]; From 455025349dad3eac733e78453209d4d31cf20497 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Thu, 16 May 2024 14:32:51 -0700 Subject: [PATCH 18/20] Rename release to 1.52.0 --- README.md | 4 ++-- RELEASE_NOTES.md | 2 +- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4bf3c0ed14a..85e13450282 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.51.9' + implementation 'com.google.android.filament:filament-android:1.52.0' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.51.9' +pod 'Filament', '~> 1.52.0' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e07e626dad4..ed35954e2f5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,7 +7,7 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). -## v1.51.9 +## v1.52.0 ## v1.51.8 diff --git a/android/gradle.properties b/android/gradle.properties index 013f0d94acd..5ec300f0168 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.51.9 +VERSION_NAME=1.52.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index ff8bb90d991..98307d9121a 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.51.9" + spec.version = "1.52.0" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.9/filament-v1.51.9-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.52.0/filament-v1.52.0-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 707476b02ec..57d43e329e0 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.51.9", + "version": "1.52.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From b4c33d2ab2674e649b69be32096bab8a724c2612 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Fri, 17 May 2024 14:00:42 -0700 Subject: [PATCH 19/20] Bump MATERIAL_VERSION to 52 --- libs/filabridge/include/filament/MaterialEnums.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 9f348481895..25eb74d63bf 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/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 = 52; /** * Supported shading models From 7ba437b2c6eda15272a6631fd3b7c250975f5891 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 16 May 2024 16:39:47 -0700 Subject: [PATCH 20/20] fix/remove wrong asserts --- filament/src/components/RenderableManager.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index c0907ee880f..8a09e0b34f0 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -966,15 +966,13 @@ void FRenderableManager::setMorphWeights(Instance instance, float const* weights void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) { - assert_invariant(offset == 0 && "Offset not yet supported."); - assert_invariant(count == morphTargetBuffer->getVertexCount() && "Count not yet supported."); if (instance) { assert_invariant(morphTargetBuffer); MorphWeights const& morphWeights = mManager[instance].morphWeights; - ASSERT_PRECONDITION(morphWeights.count == morphTargetBuffer->getCount(), + ASSERT_PRECONDITION(morphWeights.count == count, "Only %d morph targets can be set (count=%d)", - morphWeights.count, morphTargetBuffer->getCount()); + morphWeights.count, count); Slice& morphTargets = getMorphTargets(instance, level); if (primitiveIndex < morphTargets.size()) {