From 6805940bbfb13377e44f277910927af8c6a48e7b Mon Sep 17 00:00:00 2001 From: daemyung jang Date: Thu, 13 Jan 2022 04:06:47 +0900 Subject: [PATCH] Compute morphing on GPU (#4999) --- .../filament/filamat/MaterialBuilder.java | 16 +- .../src/main/cpp/RenderableManager.cpp | 4 +- .../android/filament/RenderableManager.java | 8 +- .../google/android/filament/VertexBuffer.java | 16 +- docs/webgl/reference.html | 8 - filament/CMakeLists.txt | 3 + filament/include/filament/Engine.h | 2 + filament/include/filament/MorphTargetBuffer.h | 119 +++++++++ filament/include/filament/RenderableManager.h | 17 +- filament/src/Allocators.h | 4 +- filament/src/Engine.cpp | 13 + filament/src/Material.cpp | 4 + filament/src/MorphTargetBuffer.cpp | 225 ++++++++++++++++++ filament/src/RenderPass.cpp | 15 ++ filament/src/RenderPass.h | 13 +- filament/src/RenderPrimitive.cpp | 4 + filament/src/RenderPrimitive.h | 5 + filament/src/Renderer.cpp | 3 + filament/src/Scene.cpp | 6 +- filament/src/ShadowMapManager.cpp | 2 + filament/src/View.cpp | 14 ++ filament/src/components/RenderableManager.cpp | 70 +++++- filament/src/components/RenderableManager.h | 39 ++- filament/src/details/Engine.h | 4 + filament/src/details/MorphTargetBuffer.h | 69 ++++++ filament/src/details/Scene.h | 4 +- filament/src/details/View.h | 4 + .../include/filament/MaterialEnums.h | 14 +- .../include/private/filament/EngineEnums.h | 22 +- .../include/private/filament/SibGenerator.h | 10 + .../include/private/filament/UibStructs.h | 10 +- libs/filabridge/src/SibGenerator.cpp | 15 ++ libs/filamat/src/UibGenerator.cpp | 10 +- libs/filamat/src/UibGenerator.h | 1 + libs/filamat/src/shaders/ShaderGenerator.cpp | 14 +- libs/gltfio/include/gltfio/FilamentAsset.h | 10 + libs/gltfio/src/Animator.cpp | 6 +- libs/gltfio/src/AssetLoader.cpp | 36 +-- libs/gltfio/src/FFilamentAsset.h | 9 +- libs/gltfio/src/FilamentAsset.cpp | 23 ++ libs/gltfio/src/MorphHelper.cpp | 142 ++++------- libs/gltfio/src/MorphHelper.h | 27 +-- libs/gltfio/src/ResourceLoader.cpp | 15 +- libs/viewer/include/viewer/SimpleViewer.h | 3 + libs/viewer/src/SimpleViewer.cpp | 22 ++ shaders/src/getters.vs | 43 +++- shaders/src/main.vs | 10 +- web/filament-js/filament.d.ts | 8 - web/filament-js/jsbindings.cpp | 7 +- web/filament-js/jsenums.cpp | 10 +- 50 files changed, 853 insertions(+), 305 deletions(-) create mode 100644 filament/include/filament/MorphTargetBuffer.h create mode 100644 filament/src/MorphTargetBuffer.cpp create mode 100644 filament/src/details/MorphTargetBuffer.h diff --git a/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java b/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java index 5017f18fc5..c52cd51e0c 100644 --- a/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java +++ b/android/filamat-android/src/main/java/com/google/android/filament/filamat/MaterialBuilder.java @@ -114,14 +114,14 @@ public class MaterialBuilder { BONE_INDICES, // indices of 4 bones (uvec4) BONE_WEIGHTS, // weights of the 4 bones (normalized float4) UNUSED, // reserved for future use - CUSTOM0, // custom or MORPH_POSITION_0 - CUSTOM1, // custom or MORPH_POSITION_1 - CUSTOM2, // custom or MORPH_POSITION_2 - CUSTOM3, // custom or MORPH_POSITION_3 - CUSTOM4, // custom or MORPH_TANGENTS_0 - CUSTOM5, // custom or MORPH_TANGENTS_1 - CUSTOM6, // custom or MORPH_TANGENTS_2 - CUSTOM7 // custom or MORPH_TANGENTS_3 + CUSTOM0, // custom + CUSTOM1, // custom + CUSTOM2, // custom + CUSTOM3, // custom + CUSTOM4, // custom + CUSTOM5, // custom + CUSTOM6, // custom + CUSTOM7 // custom } public enum BlendingMode { diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index cba710637c..8d3d43dcdd 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -270,9 +270,9 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env, jlong nativeRenderableManager, jint instance, jfloatArray weights) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; jfloat* vec = env->GetFloatArrayElements(weights, NULL); - math::float4 floatvec(vec[0], vec[1], vec[2], vec[3]); + jsize count = env->GetArrayLength(weights); + rm->setMorphWeights((RenderableManager::Instance)instance, vec, count); env->ReleaseFloatArrayElements(weights, vec, JNI_ABORT); - rm->setMorphWeights((RenderableManager::Instance)instance, floatvec); } extern "C" JNIEXPORT void 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 db4fdb8c83..1a5785ebe7 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 @@ -402,9 +402,6 @@ public class RenderableManager { /** * Controls if the renderable has vertex morphing targets, false by default. * - *

This is required to enable GPU morphing for up to 4 attributes. The attached VertexBuffer - * must provide data in the appropriate VertexAttribute slots (MORPH_POSITION_0 etc).

- * *

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

*/ @@ -497,14 +494,11 @@ public class RenderableManager { /** * Updates the vertex morphing weights on a renderable, all zeroes by default. * - *

This is specified using a 4-tuple, one float per morph target. If the renderable has fewer - * than 4 morph targets, then clients should fill the unused components with zeroes.

- * *

The renderable must be built with morphing enabled.

* * @see Builder#morphing */ - public void setMorphWeights(@EntityInstance int i, @NonNull @Size(min = 4) float[] weights) { + public void setMorphWeights(@EntityInstance int i, @NonNull float[] weights) { nSetMorphWeights(mNativeObject, i, weights); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java index d3494e81ef..359505d369 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java @@ -66,14 +66,14 @@ public class VertexBuffer { BONE_INDICES, // indices of 4 bones (uvec4) BONE_WEIGHTS, // weights of the 4 bones (normalized float4) UNUSED, // reserved for future use - CUSTOM0, // custom or MORPH_POSITION_0 - CUSTOM1, // custom or MORPH_POSITION_1 - CUSTOM2, // custom or MORPH_POSITION_2 - CUSTOM3, // custom or MORPH_POSITION_3 - CUSTOM4, // custom or MORPH_TANGENTS_0 - CUSTOM5, // custom or MORPH_TANGENTS_1 - CUSTOM6, // custom or MORPH_TANGENTS_2 - CUSTOM7 // custom or MORPH_TANGENTS_3 + CUSTOM0, // custom + CUSTOM1, // custom + CUSTOM2, // custom + CUSTOM3, // custom + CUSTOM4, // custom + CUSTOM5, // custom + CUSTOM6, // custom + CUSTOM7 // custom } public enum AttributeType { diff --git a/docs/webgl/reference.html b/docs/webgl/reference.html index 3fd55c7de6..6b7510906d 100644 --- a/docs/webgl/reference.html +++ b/docs/webgl/reference.html @@ -1438,14 +1438,6 @@ This defines the following functions:

  • CUSTOM5
  • CUSTOM6
  • CUSTOM7
  • -
  • MORPH_POSITION_0
  • -
  • MORPH_POSITION_1
  • -
  • MORPH_POSITION_2
  • -
  • MORPH_POSITION_3
  • -
  • MORPH_TANGENTS_0
  • -
  • MORPH_TANGENTS_1
  • -
  • MORPH_TANGENTS_2
  • -
  • MORPH_TANGENTS_3
  • diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 4049d39130..c9c996a9d3 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -28,6 +28,7 @@ set(PUBLIC_HDRS include/filament/LightManager.h include/filament/Material.h include/filament/MaterialInstance.h + include/filament/MorphTargetBuffer.h include/filament/Options.h include/filament/RenderTarget.h include/filament/RenderableManager.h @@ -69,6 +70,7 @@ set(SRCS src/Material.cpp src/MaterialInstance.cpp src/MaterialParser.cpp + src/MorphTargetBuffer.cpp src/PerViewUniforms.cpp src/PostProcessManager.cpp src/RenderPass.cpp @@ -140,6 +142,7 @@ set(PRIVATE_HDRS src/details/IndirectLight.h src/details/Material.h src/details/MaterialInstance.h + src/details/MorphTargetBuffer.h src/details/RenderTarget.h src/details/Renderer.h src/details/Scene.h diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 4db5ae7576..0a31013f66 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -39,6 +39,7 @@ class SkinningBuffer; class IndirectLight; class Material; class MaterialInstance; +class MorphTargetBuffer; class Renderer; class RenderTarget; class Scene; @@ -421,6 +422,7 @@ public: bool destroy(const Fence* p); //!< Destroys a Fence object. bool destroy(const IndexBuffer* p); //!< Destroys an IndexBuffer object. bool destroy(const SkinningBuffer* p); //!< Destroys a SkinningBuffer object. + bool destroy(const MorphTargetBuffer* p); //!< Destroys a MorphTargetBuffer object. bool destroy(const IndirectLight* p); //!< Destroys an IndirectLight object. /** diff --git a/filament/include/filament/MorphTargetBuffer.h b/filament/include/filament/MorphTargetBuffer.h new file mode 100644 index 0000000000..f3c0877039 --- /dev/null +++ b/filament/include/filament/MorphTargetBuffer.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_MORPHTARGETBUFFER_H +#define TNT_FILAMENT_MORPHTARGETBUFFER_H + +#include + +#include + +#include + + +namespace filament { + +/** + * MorphTargetBuffer is used to hold morphing data (positions and tangents). + */ +class UTILS_PUBLIC MorphTargetBuffer : public FilamentAPI { + struct BuilderDetails; + +public: + class Builder : public BuilderBase { + friend struct BuilderDetails; + public: + Builder() noexcept; + Builder(Builder const& rhs) noexcept; + Builder(Builder&& rhs) noexcept; + ~Builder() noexcept; + Builder& operator=(Builder const& rhs) noexcept; + Builder& operator=(Builder&& rhs) noexcept; + + /** + * Size of the morph targets in vertex counts. + * @param vertexCount Number of vertex counts the morph targets can hold. + * @return A reference to this Builder for chaining calls. + */ + Builder& vertexCount(size_t vertexCount) noexcept; + + /** + * Size of the morph targets in targets. + * @param count Number of targets the morph targets can hold. + * @return A reference to this Builder for chaining calls. + */ + Builder& count(size_t count) noexcept; + + /** + * Creates the MorphTargetBuffer object and returns a pointer to it. + * + * @param engine Reference to the filament::Engine to associate this MorphTargetBuffer with. + * + * @return pointer to the newly created object or nullptr if exceptions are disabled and + * an error occurred. + * + * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of + * memory or other resources. + * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. + */ + MorphTargetBuffer* build(Engine& engine); + private: + friend class FMorphTargetBuffer; + }; + + /** + * Updates the position of morph target at the index. + * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. + * @param targetIndex the index of morph target to be updated. + * @param weights pointer to at least count positions + * @param count number of position elements in positions + */ + void setPositionsAt(Engine& engine, size_t targetIndex, math::float3 const* positions, size_t count); + + /** + * Updates the position of morph target at the index. + * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. + * @param targetIndex the index of morph target to be updated. + * @param weights pointer to at least count positions + * @param count number of position elements in positions + */ + void setPositionsAt(Engine& engine, size_t targetIndex, math::float4 const* positions, size_t count); + + /** + * Updates the position of morph target at the index. + * @param engine Reference to the filament::Engine associated with this MorphTargetBuffer. + * @param targetIndex the index of morph target to be updated. + * @param tangents pointer to at least count tangents + * @param count number of tangent elements in tangents + */ + void setTangentsAt(Engine& engine, size_t targetIndex, math::short4 const* tangents, size_t count); + + /** + * Returns the vertex count of this MorphTargetBuffer. + * @return The number of vertices the MorphTargetBuffer holds. + */ + size_t getVertexCount() const noexcept; + + /** + * Returns the target count of this MorphTargetBuffer. + * @return The number of targets the MorphTargetBuffer holds. + */ + size_t getCount() const noexcept; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_MORPHTARGETBUFFER_H diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 46ef09e429..47f4fc56d4 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -41,9 +41,11 @@ class Engine; class IndexBuffer; class Material; class MaterialInstance; +class MorphTargetBuffer; class Renderer; class SkinningBuffer; class VertexBuffer; +class Texture; class FEngine; class FRenderPrimitive; @@ -300,9 +302,6 @@ public: /** * Controls if the renderable has vertex morphing targets, false by default. * - * This is required to enable GPU morphing for up to 4 attributes. The attached VertexBuffer - * must provide data in the appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). - * * See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis * to advance the animation. */ @@ -456,12 +455,16 @@ public: /** * Updates the vertex morphing weights on a renderable, all zeroes by default. * - * This is specified using a 4-tuple, one float per morph target. If the renderable has fewer - * than 4 morph targets, then clients should fill the unused components with zeroes. - * * The renderable must be built with morphing enabled, see Builder::morphing(). */ - void setMorphWeights(Instance instance, math::float4 const& weights) noexcept; + void setMorphWeights(Instance instance, float const* weights, size_t count) noexcept; + + + /** + * Associates a MorphTargetBuffer to the given primitive. + */ + void setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, MorphTargetBuffer* morphTargetBuffer) noexcept; /** * Gets the bounding box used for frustum culling. diff --git a/filament/src/Allocators.h b/filament/src/Allocators.h index 7ee44286fc..25ccab736c 100644 --- a/filament/src/Allocators.h +++ b/filament/src/Allocators.h @@ -22,11 +22,11 @@ #include "private/backend/BackendUtils.h" #ifndef FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB -# define FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB 2 +# define FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB 3 #endif #ifndef FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB -# define FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB 1 +# define FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB 2 #endif namespace filament { diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 4d7f3211bc..1edb08a520 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -333,6 +333,7 @@ void FEngine::shutdown() { cleanupResourceList(mBufferObjects); cleanupResourceList(mIndexBuffers); + cleanupResourceList(mMorphTargetBuffers); cleanupResourceList(mSkinningBuffers); cleanupResourceList(mVertexBuffers); cleanupResourceList(mTextures); @@ -567,6 +568,10 @@ FSkinningBuffer* FEngine::createSkinningBuffer(const SkinningBuffer::Builder& bu return create(mSkinningBuffers, builder); } +FMorphTargetBuffer* FEngine::createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept { + return create(mMorphTargetBuffers, builder); +} + FTexture* FEngine::createTexture(const Texture::Builder& builder) noexcept { return create(mTextures, builder); } @@ -753,6 +758,10 @@ bool FEngine::destroy(const FSkinningBuffer* p) { return terminateAndDestroy(p, mSkinningBuffers); } +bool FEngine::destroy(const FMorphTargetBuffer* p) { + return terminateAndDestroy(p, mMorphTargetBuffers); +} + inline bool FEngine::destroy(const FRenderer* p) { return terminateAndDestroy(p, mRenderers); } @@ -966,6 +975,10 @@ bool Engine::destroy(const SkinningBuffer* p) { return upcast(this)->destroy(upcast(p)); } +bool Engine::destroy(const MorphTargetBuffer* p) { + return upcast(this)->destroy(upcast(p)); +} + bool Engine::destroy(const IndirectLight* p) { return upcast(this)->destroy(upcast(p)); } diff --git a/filament/src/Material.cpp b/filament/src/Material.cpp index b534f93c8a..cc7341f782 100644 --- a/filament/src/Material.cpp +++ b/filament/src/Material.cpp @@ -378,6 +378,10 @@ Handle FMaterial::getSurfaceProgramSlow(uint8_t variantKey) const noe if (Variant(variantKey).hasSkinningOrMorphing()) { pb.setUniformBlock(BindingPoints::PER_RENDERABLE_BONES, PerRenderableUibBone::_name); + pb.setUniformBlock(BindingPoints::PER_RENDERABLE_MORPHING, PerRenderableMorphingUib::_name); + + addSamplerGroup(pb, BindingPoints::PER_RENDERABLE_MORPHING, + SibGenerator::getPerRenderPrimitiveMorphingSib(variantKey), mSamplerBindings); } addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(variantKey), mSamplerBindings); diff --git a/filament/src/MorphTargetBuffer.cpp b/filament/src/MorphTargetBuffer.cpp new file mode 100644 index 0000000000..3e123a9df9 --- /dev/null +++ b/filament/src/MorphTargetBuffer.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "details/MorphTargetBuffer.h" + +#include "private/filament/SibGenerator.h" + +#include "details/Engine.h" + +#include "FilamentAPI-impl.h" + +#include + +#include + +namespace filament { + +using namespace backend; +using namespace math; + +struct MorphTargetBuffer::BuilderDetails { + size_t mVertexCount = 0; + size_t mCount = 0; +}; + +using BuilderType = MorphTargetBuffer; +BuilderType::Builder::Builder() noexcept = default; +BuilderType::Builder::~Builder() noexcept = default; +BuilderType::Builder::Builder(BuilderType::Builder const& rhs) noexcept = default; +BuilderType::Builder::Builder(BuilderType::Builder&& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder const& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder&& rhs) noexcept = default; + +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::vertexCount(size_t vertexCount) noexcept { + mImpl->mVertexCount = vertexCount; + return *this; +} + +MorphTargetBuffer::Builder& MorphTargetBuffer::Builder::count(size_t count) noexcept { + mImpl->mCount = count; + return *this; +} + +MorphTargetBuffer* MorphTargetBuffer::Builder::build(Engine& engine) { + return upcast(engine).createMorphTargetBuffer(*this); +} + +// ------------------------------------------------------------------------------------------------ + +// This value is limited by ES3.0, ES3.0 only guarantees 2048. +// When you change this value, you must change MAX_MORPH_TARGET_BUFFER_WIDTH at getters.vs +constexpr size_t MAX_MORPH_TARGET_BUFFER_WIDTH = 2048; + +static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { ::free(mem); }; + +static inline size_t getWidth(size_t vertexCount) noexcept { + return std::min(vertexCount, MAX_MORPH_TARGET_BUFFER_WIDTH); +} + +static inline size_t getHeight(size_t vertexCount) noexcept { + return (vertexCount + MAX_MORPH_TARGET_BUFFER_WIDTH) / MAX_MORPH_TARGET_BUFFER_WIDTH; +} + +static inline size_t getDepth(size_t targetCount) noexcept { + return targetCount * 2; +} + +static inline size_t getSize(size_t vertexCount) noexcept { + const size_t stride = getWidth(vertexCount) * sizeof(float4); + return Texture::PixelBufferDescriptor::computeDataSize( + Texture::PixelBufferDescriptor::PixelDataFormat::RGBA, + Texture::PixelBufferDescriptor::PixelDataType::FLOAT, + stride, getHeight(vertexCount), 1); +} + +static inline size_t getPositionIndex(size_t targetIndex) noexcept { + return targetIndex * 2 + 0; +} + +static inline size_t getTangentIndex(size_t targetIndex) noexcept { + return targetIndex * 2 + 1; +} + +FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) + : mSBuffer(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT), + mVertexCount(builder->mVertexCount), + mCount(builder->mCount) { + FEngine::DriverApi& driver = engine.getDriverApi(); + + mSbHandle = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT); + mTbHandle = driver.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1, TextureFormat::RGBA32F, 1, + getWidth(mVertexCount), getHeight(mVertexCount), getDepth(mCount), + TextureUsage::DEFAULT); + + SamplerParams samplerParams{}; + samplerParams.filterMin = SamplerMinFilter::NEAREST; + samplerParams.filterMag = SamplerMagFilter::NEAREST; + mSBuffer.setSampler(PerRenderPrimitiveMorphingSib::TARGETS, mTbHandle, samplerParams); +} + +void FMorphTargetBuffer::terminate(FEngine& engine) { + FEngine::DriverApi& driver = engine.getDriverApi(); + + driver.destroySamplerGroup(mSbHandle); + driver.destroyTexture(mTbHandle); +} + +void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, math::float3 const* positions, size_t count) { + ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count"); + + auto size = getSize(mVertexCount); + + ASSERT_PRECONDITION((int)sizeof(math::float3) * count <= size, + "MorphTargetBuffer (size=%lu) overflow (size=%lu)", + size, sizeof(math::float3) * count); + + // We could use a pool instead of malloc() directly. + auto* out = (float4*) malloc(size); + std::transform(positions, positions + count, out, + [](const float3& p) { return float4(p, 1.0f); }); + + Texture::PixelBufferDescriptor buffer(out, size, Texture::Format::RGBA, Texture::Type::FLOAT, + FREE_CALLBACK); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.update3DImage(mTbHandle, 0, 0, 0, getPositionIndex(targetIndex), + getWidth(mVertexCount), getHeight(mVertexCount), 1, + std::move(buffer)); +} + +void FMorphTargetBuffer::setPositionsAt(FEngine& engine, size_t targetIndex, math::float4 const* positions, size_t count) { + ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count"); + + auto size = getSize(mVertexCount); + + ASSERT_PRECONDITION((int)sizeof(math::float4) * count <= size, + "MorphTargetBuffer (size=%lu) overflow (size=%lu)", + size, sizeof(math::float4) * count); + + // We could use a pool instead of malloc() directly. + auto* out = (float4*) malloc(size); + memcpy(out, positions, sizeof(math::float4) * count); + + Texture::PixelBufferDescriptor buffer(out, size, Texture::Format::RGBA, Texture::Type::FLOAT, + FREE_CALLBACK); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.update3DImage(mTbHandle, 0, 0, 0, getPositionIndex(targetIndex), + getWidth(mVertexCount), getHeight(mVertexCount), 1, + std::move(buffer)); +} + +void FMorphTargetBuffer::setTangentsAt(FEngine& engine, size_t targetIndex, math::short4 const* tangents, size_t count) { + ASSERT_PRECONDITION(targetIndex < mCount, "targetIndex must be < count"); + + auto size = getSize(mVertexCount); + + ASSERT_PRECONDITION((int)sizeof(math::short4) * count <= size, + "MorphTargetBuffer (size=%lu) overflow (size=%lu)", + size, sizeof(math::short4) * count); + + // We could use a pool instead of malloc() directly. + auto* out = (float4*) malloc(size); + std::transform(tangents, tangents + count, out, + [](const short4& t) { return unpackSnorm16(t); }); + + Texture::PixelBufferDescriptor buffer(out, size, Texture::Format::RGBA, Texture::Type::FLOAT, + FREE_CALLBACK); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.update3DImage(mTbHandle, 0, 0, 0, getTangentIndex(targetIndex), + getWidth(mVertexCount), getHeight(mVertexCount), 1, + std::move(buffer)); +} + +void FMorphTargetBuffer::commit(FEngine& engine) const noexcept { + if (mSBuffer.isDirty()) { + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.updateSamplerGroup(mSbHandle, std::move(mSBuffer.toCommandStream())); + } +} + +void FMorphTargetBuffer::bind(backend::DriverApi& driver) const noexcept { + driver.bindSamplers(BindingPoints::PER_RENDERABLE_MORPHING, mSbHandle); +} + +// ------------------------------------------------------------------------------------------------ +// Trampoline calling into private implementation +// ------------------------------------------------------------------------------------------------ + +void MorphTargetBuffer::setPositionsAt(Engine& engine, size_t targetIndex, math::float3 const* positions, size_t count) { + upcast(this)->setPositionsAt(upcast(engine), targetIndex, positions, count); +} + +void MorphTargetBuffer::setPositionsAt(Engine& engine, size_t targetIndex, math::float4 const* positions, size_t count) { + upcast(this)->setPositionsAt(upcast(engine), targetIndex, positions, count); +} + +void MorphTargetBuffer::setTangentsAt(Engine& engine, size_t targetIndex, math::short4 const* tangents, size_t count) { + upcast(this)->setTangentsAt(upcast(engine), targetIndex, tangents, count); +} + +size_t MorphTargetBuffer::getVertexCount() const noexcept { + return upcast(this)->getVertexCount(); +} + +size_t MorphTargetBuffer::getCount() const noexcept { + return upcast(this)->getCount(); +} + +} // namespace filament + diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 708d563ff4..89a1d9d13c 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -381,6 +381,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, FMaterialInstance const* const mi = primitive.getMaterialInstance(); if constexpr (isColorPass) { cmdColor.primitive.primitiveHandle = primitive.getHwHandle(); + cmdColor.primitive.morphTargetBuffer = primitive.getMorphTargetBuffer(); cmdColor.primitive.materialVariant = materialVariant; RenderPass::setupColorCommand(cmdColor, mi, inverseFrontFaces); @@ -470,6 +471,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, // unconditionally write the command cmdDepth.primitive.primitiveHandle = primitive.getHwHandle(); cmdDepth.primitive.mi = mi; + cmdDepth.primitive.morphTargetBuffer = primitive.getMorphTargetBuffer(); cmdDepth.primitive.rasterState.culling = mi->getCullingMode(); // FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()? @@ -529,6 +531,7 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver, SYSTRACE_VALUE32("commandCount", last - first); auto const* const UTILS_RESTRICT soaSkinning = soa.data(); + auto const* const UTILS_RESTRICT soaMorphing = soa.data(); PolygonOffset dummyPolyOffset; PipelineState pipeline{ .polygonOffset = mPolygonOffset }; @@ -577,6 +580,18 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver, skinning.offset * sizeof(PerRenderableUibBone), CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone)); } + + auto morphTargetBuffer = info.morphTargetBuffer; + if (UTILS_UNLIKELY(morphTargetBuffer)) { + // Instead of using a UBO per primitive, we could also have a single UBO for all primitives + // and use bindUniformBufferRange which might be more efficient. + auto morphing = soaMorphing[info.index]; + assert_invariant(morphing.handle); + driver.bindUniformBuffer(BindingPoints::PER_RENDERABLE_MORPHING, morphing.handle); + assert_invariant(morphing.count <= morphTargetBuffer->getCount()); + morphTargetBuffer->bind(driver); + } + driver.draw(pipeline, info.primitiveHandle); } } diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index c1fa00eea2..5bd930f868 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -208,19 +208,20 @@ public: return boolish ? std::numeric_limits::max() : uint64_t(0); } - struct PrimitiveInfo { // 24 bytes + struct PrimitiveInfo { // 32 bytes FMaterialInstance const* mi = nullptr; // 8 bytes (4) + FMorphTargetBuffer const* morphTargetBuffer = nullptr; // 8 bytes (4) backend::Handle primitiveHandle; // 4 bytes backend::RasterState rasterState; // 4 bytes uint16_t index = 0; // 2 bytes Variant materialVariant; // 1 byte - uint8_t reserved[13 - sizeof(void*)] = {}; // 5 byte (9) + uint8_t reserved[21 - sizeof(void*) - sizeof(void*)] = {}; // 5 bytes (13) }; - static_assert(sizeof(PrimitiveInfo) == 24); + static_assert(sizeof(PrimitiveInfo) == 32); - struct alignas(8) Command { // 32 bytes + struct alignas(8) Command { // 40 bytes CommandKey key = 0; // 8 bytes - PrimitiveInfo primitive; // 24 bytes + PrimitiveInfo primitive; // 32 bytes bool operator < (Command const& rhs) const noexcept { return key < rhs.key; } // placement new declared as "throw" to avoid the compiler's null-check inline void* operator new (std::size_t, void* ptr) { @@ -228,7 +229,7 @@ public: return ptr; } }; - static_assert(sizeof(Command) == 32); + static_assert(sizeof(Command) == 40); static_assert(std::is_trivially_destructible_v, "Command isn't trivially destructible"); diff --git a/filament/src/RenderPrimitive.cpp b/filament/src/RenderPrimitive.cpp index a6eae017d2..469bbb3e1d 100644 --- a/filament/src/RenderPrimitive.cpp +++ b/filament/src/RenderPrimitive.cpp @@ -83,4 +83,8 @@ void FRenderPrimitive::set(FEngine& engine, RenderableManager::PrimitiveType typ mPrimitiveType = type; } +void FRenderPrimitive::set(FMorphTargetBuffer* morphTargetBuffer) noexcept { + mMorphTargetBuffer = morphTargetBuffer; +} + } // namespace filament diff --git a/filament/src/RenderPrimitive.h b/filament/src/RenderPrimitive.h index 70188c3238..cd7da72f0e 100644 --- a/filament/src/RenderPrimitive.h +++ b/filament/src/RenderPrimitive.h @@ -30,6 +30,7 @@ namespace filament { class FEngine; class FVertexBuffer; class FIndexBuffer; +class FMorphTargetBuffer; class FRenderer; class FRenderPrimitive { @@ -45,6 +46,8 @@ public: void set(FEngine& engine, RenderableManager::PrimitiveType type, size_t offset, size_t minIndex, size_t maxIndex, size_t count) noexcept; + void set(FMorphTargetBuffer* morphTargetBuffer) noexcept; + // frees driver resources, object becomes invalid void terminate(FEngine& engine); @@ -53,6 +56,7 @@ public: backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; } AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; } uint16_t getBlendOrder() const noexcept { return mBlendOrder; } + FMorphTargetBuffer* getMorphTargetBuffer() const noexcept { return mMorphTargetBuffer; } void setMaterialInstance(FMaterialInstance const* mi) noexcept { mMaterialInstance = mi; } void setBlendOrder(uint16_t order) noexcept { @@ -65,6 +69,7 @@ private: backend::PrimitiveType mPrimitiveType = backend::PrimitiveType::NONE; AttributeBitset mEnabledAttributes; uint16_t mBlendOrder = 0; + FMorphTargetBuffer* mMorphTargetBuffer = nullptr; }; } // namespace filament diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 52d5628b2e..d7c805ad9e 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -416,6 +416,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { // of RenderPass::setCamera / RenderPass::setGeometry calls. view.updatePrimitivesLod(engine, cameraInfo, scene.getRenderableData(), view.getVisibleRenderables()); + // updatePrimitivesMorphTargetBuffer must be run after updatePrimitivesLod. + view.updatePrimitivesMorphTargetBuffer(engine, cameraInfo, + scene.getRenderableData(), view.getVisibleRenderables()); pass.setCamera(cameraInfo); pass.setGeometry(scene.getRenderableData(), view.getVisibleRenderables(), scene.getRenderableUBO()); diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index d5cca3c3c2..ca64d72c43 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -138,9 +138,9 @@ void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCa worldTransform, // WORLD_TRANSFORM visibility, // VISIBILITY_STATE rcm.getSkinningBufferInfo(ri), // SKINNING_BUFFER + rcm.getMorphingBufferInfo(ri), // MORPHING_BUFFER worldAABB.center, // WORLD_AABB_CENTER 0, // VISIBLE_MASK - rcm.getMorphWeights(ri), // MORPH_WEIGHTS rcm.getChannels(ri), // CHANNELS rcm.getLayerMask(ri), // LAYERS worldAABB.halfExtent, // WORLD_AABB_EXTENT @@ -254,8 +254,8 @@ void FScene::updateUBOs(utils::Range visibleRenderables, backend::Hand visibility.screenSpaceContactShadows)); UniformBuffer::setUniform(buffer, - offset + offsetof(PerRenderableUib, morphWeights), - sceneData.elementAt(i)); + offset + offsetof(PerRenderableUib, morphTargetCount), + sceneData.elementAt(i).count); UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, channels), diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 1614e231cc..49fe876d10 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -264,6 +264,8 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp // updatePrimitivesLod must be run before RenderPass::appendCommands. view.updatePrimitivesLod(engine, cameraInfo, scene->getRenderableData(), entry.range); + // updatePrimitivesMorphTargetBuffer must be run after updatePrimitivesLod. + view.updatePrimitivesMorphTargetBuffer(engine, cameraInfo, scene->getRenderableData(), entry.range); // generate and sort the commands for rendering the shadow map RenderPass entryPass(pass); diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 7b2a3ceacf..0e9cf8f43c 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -19,6 +19,7 @@ #include "Culler.h" #include "DFG.h" #include "Froxelizer.h" +#include "RenderPrimitive.h" #include "ResourceAllocator.h" #include "details/Engine.h" @@ -868,6 +869,19 @@ void FView::updatePrimitivesLod(FEngine& engine, const CameraInfo&, } } +void FView::updatePrimitivesMorphTargetBuffer(FEngine& engine, const CameraInfo&, + FScene::RenderableSoa& renderableData, Range visible) noexcept { + for (uint32_t index : visible) { + Slice primitives = renderableData.elementAt(index); + for (auto& primitive : primitives) { + auto morphTargetBuffer = primitive.getMorphTargetBuffer(); + if (morphTargetBuffer) { + morphTargetBuffer->commit(engine); + } + } + } +} + void FView::renderShadowMaps(FrameGraph& fg, FEngine& engine, FEngine::DriverApi& driver, RenderPass const& pass) noexcept { mShadowMapManager.render(fg, engine, driver, pass, *this); diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 7208771bb6..d89cc4fb00 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -23,8 +23,11 @@ #include "details/Engine.h" #include "details/VertexBuffer.h" #include "details/IndexBuffer.h" +#include "details/Texture.h" #include "details/Material.h" +#include "private/filament/SibGenerator.h" + #include #include @@ -316,7 +319,6 @@ void FRenderableManager::create( setCulling(ci, builder->mCulling); setSkinning(ci, false); setMorphing(ci, builder->mMorphingEnabled); - setMorphWeights(ci, {0, 0, 0, 0}); mManager[ci].channels = builder->mChannels; const uint32_t count = builder->mSkinningBoneCount; @@ -372,6 +374,18 @@ void FRenderableManager::create( } } } + + if (builder->mMorphingEnabled) { + // Instead of using a UBO per primitive, we could also have a single UBO for all primitives + // and use bindUniformBufferRange which might be more efficient. + MorphWeights& morphWeights = manager[ci].morphWeights; + morphWeights = MorphWeights { + .handle = driver.createBufferObject( + sizeof(PerRenderableMorphingUib), + BufferObjectBinding::UNIFORM, + backend::BufferUsage::DYNAMIC), + .count = 0 }; + } } engine.flushIfNeeded(); } @@ -416,6 +430,12 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept { if (bones.handle && !bones.skinningBufferMode) { driver.destroyBufferObject(bones.handle); } + + // destroy the weights structures if any + MorphWeights const& morphWeights = manager[ci].morphWeights; + if (morphWeights.handle) { + driver.destroyBufferObject(morphWeights.handle); + } } void FRenderableManager::destroyComponentPrimitives( @@ -562,9 +582,38 @@ void FRenderableManager::setSkinningBuffer(FRenderableManager::Instance ci, bones.offset = uint16_t(offset); } -void FRenderableManager::setMorphWeights(Instance ci, const float4& weights) noexcept { - if (ci) { - mManager[ci].morphWeights = weights; +static void updateMorphWeights(FEngine& engine, backend::Handle handle, + float const* weights, size_t count) noexcept { + auto& driver = engine.getDriverApi(); + auto size = sizeof(PerRenderableMorphingUib); + auto* UTILS_RESTRICT out = (PerRenderableMorphingUib*)driver.allocate(size); + memset(out, 0, size); + std::transform(weights, weights + count, out->weights, + [](float value) { return float4(value); }); + driver.updateBufferObject(handle, { out, size }, 0); +} + +void FRenderableManager::setMorphWeights(Instance instance, float const* weights, size_t count) noexcept { + if (instance) { + MorphWeights& morphWeights = mManager[instance].morphWeights; + morphWeights.count = count; + + ASSERT_PRECONDITION(count < CONFIG_MAX_MORPH_TARGET_COUNT, + "Only %d morph targets are supported (count=%d)", CONFIG_MAX_MORPH_TARGET_COUNT, count); + + if (morphWeights.handle) { + updateMorphWeights(mEngine, morphWeights.handle, weights, count); + } + } +} + +void FRenderableManager::setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer) noexcept { + if (instance) { + Slice& primitives = getRenderPrimitives(instance, 0); + if (primitiveIndex < primitives.size()) { + primitives[primitiveIndex].set(morphTargetBuffer); + } } } @@ -693,15 +742,20 @@ void RenderableManager::setBones(Instance instance, upcast(this)->setBones(instance, transforms, boneCount, offset); } -void RenderableManager::setMorphWeights(Instance instance, float4 const& weights) noexcept { - upcast(this)->setMorphWeights(instance, weights); -} - void RenderableManager::setSkinningBuffer(Instance instance, SkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept { upcast(this)->setSkinningBuffer(instance, upcast(skinningBuffer), count, offset); } +void RenderableManager::setMorphWeights(Instance instance, float const* weights, size_t count) noexcept { + upcast(this)->setMorphWeights(instance, weights, count); +} + +void RenderableManager::setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, MorphTargetBuffer* morphTargetBuffer) noexcept { + upcast(this)->setMorphTargetBufferAt(instance, primitiveIndex, upcast(morphTargetBuffer)); +} + void RenderableManager::setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept { upcast(this)->setLightChannel(instance, channel, enable); } diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 364a2f3c05..38db7a3794 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -40,9 +40,11 @@ namespace filament { class FBufferObject; class FIndexBuffer; class FMaterialInstance; +class FMorphTargetBuffer; class FRenderPrimitive; class FSkinningBuffer; class FVertexBuffer; +class FTexture; class FRenderableManager : public RenderableManager { public: @@ -106,9 +108,11 @@ public: inline void setPrimitives(Instance instance, utils::Slice const& primitives) noexcept; inline void setBones(Instance instance, Bone const* transforms, size_t boneCount, size_t offset = 0) noexcept; inline void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount, size_t offset = 0) noexcept; - inline void setMorphWeights(Instance instance, const math::float4& weights) noexcept; inline void setSkinningBuffer(Instance instance, FSkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept; + void setMorphWeights(Instance instance, float const* weights, size_t count) noexcept; + void setMorphTargetBufferAt(Instance instance, + size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer) noexcept; inline void setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept; inline bool getLightChannel(Instance instance, unsigned int channel) const noexcept; @@ -123,7 +127,6 @@ public: inline Visibility getVisibility(Instance instance) const noexcept; inline uint8_t getLayerMask(Instance instance) const noexcept; inline uint8_t getPriority(Instance instance) const noexcept; - inline math::float4 getMorphWeights(Instance instance) const noexcept; inline uint8_t getChannels(Instance instance) const noexcept; struct SkinningBindingInfo { @@ -134,6 +137,12 @@ public: inline SkinningBindingInfo getSkinningBufferInfo(Instance instance) const noexcept; inline uint32_t getBoneCount(Instance instance) const noexcept; + struct MorphingBindingInfo { + backend::Handle handle; + uint32_t count; + }; + inline MorphingBindingInfo getMorphingBufferInfo(Instance instance) const noexcept; + inline int getMorphTargetCount(Instance instance) const noexcept; utils::Entity getEntity(Instance instance) const noexcept { return mManager.getEntity(instance); @@ -165,13 +174,18 @@ private: uint16_t offset = 0; bool skinningBufferMode = false; }; - static_assert(sizeof(Bones) == 12); + struct MorphWeights { + backend::Handle handle; + uint32_t count = 0; + }; + static_assert(sizeof(MorphWeights) == 8); + enum { AABB, // user data LAYERS, // user data - MORPH_WEIGHTS, // user data + MORPH_WEIGHTS, // filament data, UBO storing a pointer to the morph weights information CHANNELS, // user data VISIBILITY, // user data PRIMITIVES, // user data @@ -181,7 +195,7 @@ private: using Base = utils::SingleInstanceComponentManager< Box, // AABB uint8_t, // LAYERS - math::float4, // MORPH_WEIGHTS + MorphWeights, // MORPH_WEIGHTS uint8_t, // CHANNELS Visibility, // VISIBILITY utils::Slice, // PRIMITIVES @@ -325,10 +339,6 @@ uint8_t FRenderableManager::getPriority(Instance instance) const noexcept { return getVisibility(instance).priority; } -math::float4 FRenderableManager::getMorphWeights(Instance instance) const noexcept { - return mManager[instance].morphWeights; -} - uint8_t FRenderableManager::getChannels(Instance instance) const noexcept { return mManager[instance].channels; } @@ -348,6 +358,17 @@ inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexce return bones.count; } +FRenderableManager::MorphingBindingInfo +FRenderableManager::getMorphingBufferInfo(Instance instance) const noexcept { + MorphWeights const& morphWeights = mManager[instance].morphWeights; + return { morphWeights.handle, morphWeights.count }; +} + +int FRenderableManager::getMorphTargetCount(Instance instance) const noexcept { + MorphWeights const& morphWeights = mManager[instance].morphWeights; + return morphWeights.count; +} + utils::Slice const& FRenderableManager::getRenderPrimitives( Instance instance, uint8_t level) const noexcept { return mManager[instance].primitives; diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 6fd2777c45..9f6e1e1b0e 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -36,6 +36,7 @@ #include "details/IndexBuffer.h" #include "details/RenderTarget.h" #include "details/SkinningBuffer.h" +#include "details/MorphTargetBuffer.h" #include "details/Skybox.h" #include "private/backend/CommandBufferQueue.h" @@ -234,6 +235,7 @@ public: FVertexBuffer* createVertexBuffer(const VertexBuffer::Builder& builder) noexcept; FIndexBuffer* createIndexBuffer(const IndexBuffer::Builder& builder) noexcept; FSkinningBuffer* createSkinningBuffer(const SkinningBuffer::Builder& builder) noexcept; + FMorphTargetBuffer* createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept; FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept; FMaterial* createMaterial(const Material::Builder& builder) noexcept; FTexture* createTexture(const Texture::Builder& builder) noexcept; @@ -265,6 +267,7 @@ public: bool destroy(const FFence* p); bool destroy(const FIndexBuffer* p); bool destroy(const FSkinningBuffer* p); + bool destroy(const FMorphTargetBuffer* p); bool destroy(const FIndirectLight* p); bool destroy(const FMaterial* p); bool destroy(const FMaterialInstance* p); @@ -376,6 +379,7 @@ private: ResourceList mStreams{ "Stream" }; ResourceList mIndexBuffers{ "IndexBuffer" }; ResourceList mSkinningBuffers{ "SkinningBuffer" }; + ResourceList mMorphTargetBuffers{ "MorphTargetBuffer" }; ResourceList mVertexBuffers{ "VertexBuffer" }; ResourceList mIndirectLights{ "IndirectLight" }; ResourceList mMaterials{ "Material" }; diff --git a/filament/src/details/MorphTargetBuffer.h b/filament/src/details/MorphTargetBuffer.h new file mode 100644 index 0000000000..5ba5fe1fdd --- /dev/null +++ b/filament/src/details/MorphTargetBuffer.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DETAILS_MORPHTARGETBUFFER_H +#define TNT_FILAMENT_DETAILS_MORPHTARGETBUFFER_H + +#include "upcast.h" + +#include + +#include "private/backend/DriverApiForward.h" + +#include "private/backend/SamplerGroup.h" + +#include + +#include + +#include + +namespace filament { + +class FEngine; + +class FMorphTargetBuffer : public MorphTargetBuffer { +public: + FMorphTargetBuffer(FEngine& engine, const Builder& builder); + + // frees driver resources, object becomes invalid + void terminate(FEngine& engine); + + void setPositionsAt(FEngine& engine, size_t targetIndex, math::float3 const* positions, size_t count); + void setPositionsAt(FEngine& engine, size_t targetIndex, math::float4 const* positions, size_t count); + void setTangentsAt(FEngine& engine, size_t targetIndex, math::short4 const* tangents, size_t count); + inline size_t getVertexCount() const noexcept { return mVertexCount; } + inline size_t getCount() const noexcept { return mCount; } + +private: + friend class FView; + friend class RenderPass; + + void commit(FEngine& engine) const noexcept; + void bind(backend::DriverApi& driver) const noexcept; + + backend::Handle mSbHandle; + backend::Handle mTbHandle; + backend::SamplerGroup mSBuffer; + size_t mVertexCount; + size_t mCount; +}; + +FILAMENT_UPCAST(MorphTargetBuffer) + +} // namespace filament + +#endif //TNT_FILAMENT_DETAILS_MORPHTARGETBUFFER_H diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 03833a86dc..f8f66afbf6 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -105,9 +105,9 @@ public: WORLD_TRANSFORM, // 16 | instance of the Transform component VISIBILITY_STATE, // 1 | visibility data of the component SKINNING_BUFFER, // 8 | bones uniform buffer handle, count, offset + MORPHING_BUFFER, // 8 | weights uniform buffer handle, count WORLD_AABB_CENTER, // 12 | world-space bounding box center of the renderable VISIBLE_MASK, // 2 | each bit represents a visibility in a pass - MORPH_WEIGHTS, // 4 | floats for morphing CHANNELS, // 1 | currently light channels only // These are not needed anymore after culling @@ -127,9 +127,9 @@ public: math::mat4f, // WORLD_TRANSFORM FRenderableManager::Visibility, // VISIBILITY_STATE FRenderableManager::SkinningBindingInfo, // SKINNING_BUFFER + FRenderableManager::MorphingBindingInfo, // MORPHING_BUFFER math::float3, // WORLD_AABB_CENTER VisibleMaskType, // VISIBLE_MASK - math::float4, // MORPH_WEIGHTS uint8_t, // CHANNELS uint8_t, // LAYERS math::float3, // WORLD_AABB_EXTENT diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 68b0447573..d474574bdf 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -198,6 +198,10 @@ public: FEngine& engine, const CameraInfo& camera, FScene::RenderableSoa& renderableData, Range visible) noexcept; + void updatePrimitivesMorphTargetBuffer( + FEngine& engine, const CameraInfo&, + FScene::RenderableSoa& renderableData, Range visible) noexcept; + void setShadowingEnabled(bool enabled) noexcept { mShadowingEnabled = enabled; } bool isShadowingEnabled() const noexcept { return mShadowingEnabled; } diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 9e2ed51906..dbca04195f 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -27,7 +27,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 16; +static constexpr size_t MATERIAL_VERSION = 17; /** * Supported shading models @@ -136,20 +136,10 @@ enum VertexAttribute : uint8_t { CUSTOM6 = 14, CUSTOM7 = 15, - // Aliases for vertex morphing. - MORPH_POSITION_0 = CUSTOM0, - MORPH_POSITION_1 = CUSTOM1, - MORPH_POSITION_2 = CUSTOM2, - MORPH_POSITION_3 = CUSTOM3, - MORPH_TANGENTS_0 = CUSTOM4, - MORPH_TANGENTS_1 = CUSTOM5, - MORPH_TANGENTS_2 = CUSTOM6, - MORPH_TANGENTS_3 = CUSTOM7, - // this is limited by driver::MAX_VERTEX_ATTRIBUTE_COUNT }; -static constexpr size_t MAX_MORPH_TARGETS = 4; +static constexpr size_t MAX_MORPH_TARGETS = 128; // this is limited by filament::CONFIG_MAX_MORPH_TARGET_COUNT static constexpr size_t MAX_CUSTOM_ATTRIBUTES = 8; /** diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 30a3fb343f..ccdace432b 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -33,14 +33,15 @@ enum class PostProcessVariant : uint8_t { // Binding points for uniform buffers and sampler buffers. // Effectively, these are just names. namespace BindingPoints { - constexpr uint8_t PER_VIEW = 0; // uniforms/samplers updated per view - constexpr uint8_t PER_RENDERABLE = 1; // uniforms/samplers updated per renderable - constexpr uint8_t PER_RENDERABLE_BONES = 2; // bones data, per renderable - constexpr uint8_t LIGHTS = 3; // lights data array - constexpr uint8_t SHADOW = 4; // punctual shadow data - constexpr uint8_t FROXEL_RECORDS = 5; - constexpr uint8_t PER_MATERIAL_INSTANCE = 6; // uniforms/samplers updates per material - constexpr uint8_t COUNT = 7; + constexpr uint8_t PER_VIEW = 0; // uniforms/samplers updated per view + constexpr uint8_t PER_RENDERABLE = 1; // uniforms/samplers updated per renderable + constexpr uint8_t PER_RENDERABLE_BONES = 2; // bones data, per renderable + constexpr uint8_t PER_RENDERABLE_MORPHING = 3; // morphing uniform/sampler updated per render primitive + constexpr uint8_t LIGHTS = 4; // lights data array + constexpr uint8_t SHADOW = 5; // punctual shadow data + constexpr uint8_t FROXEL_RECORDS = 6; + constexpr uint8_t PER_MATERIAL_INSTANCE = 7; // uniforms/samplers updates per material + constexpr uint8_t COUNT = 8; // These are limited by Program::UNIFORM_BINDING_COUNT (currently 8) } @@ -66,6 +67,11 @@ constexpr size_t CONFIG_MAX_SHADOW_CASCADES = 4; // We store 64 bytes per bone. constexpr size_t CONFIG_MAX_BONE_COUNT = 256; +// The maximum number of morph target count. +// This value is limited by ES3.0, ES3.0 only guarantees 256 layers in an array texture. +// For morphing, 128 layers are used for the positions and others are used for tangents. +constexpr int CONFIG_MAX_MORPH_TARGET_COUNT = 128; + } // namespace filament #endif // TNT_FILAMENT_driver/EngineEnums.h diff --git a/libs/filabridge/include/private/filament/SibGenerator.h b/libs/filabridge/include/private/filament/SibGenerator.h index 25c9f7362c..610ee07191 100644 --- a/libs/filabridge/include/private/filament/SibGenerator.h +++ b/libs/filabridge/include/private/filament/SibGenerator.h @@ -30,6 +30,7 @@ class SamplerInterfaceBlock; class SibGenerator { public: static SamplerInterfaceBlock const& getPerViewSib(uint8_t variantKey) noexcept; + static SamplerInterfaceBlock const& getPerRenderPrimitiveMorphingSib(uint8_t variantKey) noexcept; static SamplerInterfaceBlock const* getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept; // When adding a sampler block here, make sure to also update // FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed @@ -48,5 +49,14 @@ struct PerViewSib { static constexpr size_t SAMPLER_COUNT = 7; }; +struct PerRenderPrimitiveMorphingSib { + // Morph positions and normals are packed into one texture. + // Because some materials exceed MAX_SAMPLER_COUNT(16) + // when morph positions and normals are bound to samplers separately. + static constexpr size_t TARGETS = 0; // morph positions and normals + + static constexpr size_t SAMPLER_COUNT = 1; +}; + } #endif // TNT_FILABRIDGE_SIBGENERATOR_H diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 4a25bac3d0..aafbe10751 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -149,7 +149,7 @@ struct alignas(256) PerRenderableUib { // NOLINT(cppcoreguidelines-pro-type-memb static constexpr utils::StaticString _name{ "ObjectUniforms" }; math::mat4f worldFromModelMatrix; math::mat3f worldFromModelNormalMatrix; // this gets expanded to 48 bytes during the copy to the UBO - alignas(16) math::float4 morphWeights; // morph weights (we could easily have 8 using half) + alignas(16) uint32_t morphTargetCount; uint32_t flags; // see packFlags() below uint32_t channels; // 0x000000ll uint32_t objectId; // used for picking @@ -227,6 +227,14 @@ struct PerRenderableUibBone { // NOLINT(cppcoreguidelines-pro-type-member-init) static_assert(CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone) <= 16384, "PerRenderableUibBone exceed max UBO size"); +struct alignas(16) PerRenderableMorphingUib { + static constexpr utils::StaticString _name{ "MorphingUniforms" }; + // The array stride(the bytes between array elements) is always rounded up to the size of a vec4 in std140. + math::float4 weights[CONFIG_MAX_MORPH_TARGET_COUNT]; +}; +static_assert(sizeof(PerRenderableMorphingUib) <= 16384, + "PerRenderableMorphingUib exceed max UBO size"); + } // namespace filament #endif // TNT_FILABRIDGE_UIBSTRUCTS_H diff --git a/libs/filabridge/src/SibGenerator.cpp b/libs/filabridge/src/SibGenerator.cpp index 14b1bab85f..a361f701c9 100644 --- a/libs/filabridge/src/SibGenerator.cpp +++ b/libs/filabridge/src/SibGenerator.cpp @@ -66,12 +66,27 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(uint8_t variantKey) noe return v.hasVsm() ? sibVsm : sibPcf; } +SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveMorphingSib(uint8_t variantKey) noexcept { + using Type = SamplerInterfaceBlock::Type; + using Format = SamplerInterfaceBlock::Format; + using Precision = SamplerInterfaceBlock::Precision; + + static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder() + .name("Morphing") + .add("targets", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH) + .build(); + + return sib; +} + SamplerInterfaceBlock const* SibGenerator::getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept { switch (bindingPoint) { case BindingPoints::PER_VIEW: return &getPerViewSib(variantKey); case BindingPoints::PER_RENDERABLE: return nullptr; + case BindingPoints::PER_RENDERABLE_MORPHING: + return &getPerRenderPrimitiveMorphingSib(variantKey); case BindingPoints::LIGHTS: return nullptr; default: diff --git a/libs/filamat/src/UibGenerator.cpp b/libs/filamat/src/UibGenerator.cpp index 8708be1d1f..a64144603c 100644 --- a/libs/filamat/src/UibGenerator.cpp +++ b/libs/filamat/src/UibGenerator.cpp @@ -128,7 +128,7 @@ UniformInterfaceBlock const& UibGenerator::getPerRenderableUib() noexcept { .name(PerRenderableUib::_name) .add("worldFromModelMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH) .add("worldFromModelNormalMatrix", 1, UniformInterfaceBlock::Type::MAT3, Precision::HIGH) - .add("morphWeights", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH) + .add("morphTargetCount", 1, UniformInterfaceBlock::Type::UINT) .add("flags", 1, UniformInterfaceBlock::Type::UINT) .add("channels", 1, UniformInterfaceBlock::Type::UINT) .add("objectId", 1, UniformInterfaceBlock::Type::UINT) @@ -161,6 +161,14 @@ UniformInterfaceBlock const& UibGenerator::getPerRenderableBonesUib() noexcept { return uib; } +UniformInterfaceBlock const& UibGenerator::getPerRenderableMorphingUib() noexcept { + static UniformInterfaceBlock uib = UniformInterfaceBlock::Builder() + .name(PerRenderableMorphingUib::_name) + .add("weights", CONFIG_MAX_MORPH_TARGET_COUNT, UniformInterfaceBlock::Type::FLOAT4) + .build(); + return uib; +} + UniformInterfaceBlock const& UibGenerator::getFroxelRecordUib() noexcept { static UniformInterfaceBlock uib = UniformInterfaceBlock::Builder() .name(FroxelRecordUib::_name) diff --git a/libs/filamat/src/UibGenerator.h b/libs/filamat/src/UibGenerator.h index 4007c5c76b..8c39ebcc96 100644 --- a/libs/filamat/src/UibGenerator.h +++ b/libs/filamat/src/UibGenerator.h @@ -28,6 +28,7 @@ public: static UniformInterfaceBlock const& getLightsUib() noexcept; static UniformInterfaceBlock const& getShadowUib() noexcept; static UniformInterfaceBlock const& getPerRenderableBonesUib() noexcept; + static UniformInterfaceBlock const& getPerRenderableMorphingUib() noexcept; static UniformInterfaceBlock const& getFroxelRecordUib() noexcept; // When adding an UBO here, make sure to also update // FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index fa4449e2bb..37cc762a10 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -218,14 +218,6 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, if (variant.hasSkinningOrMorphing()) { attributes.set(VertexAttribute::BONE_INDICES); attributes.set(VertexAttribute::BONE_WEIGHTS); - attributes.set(VertexAttribute::MORPH_POSITION_0); - attributes.set(VertexAttribute::MORPH_POSITION_1); - attributes.set(VertexAttribute::MORPH_POSITION_2); - attributes.set(VertexAttribute::MORPH_POSITION_3); - attributes.set(VertexAttribute::MORPH_TANGENTS_0); - attributes.set(VertexAttribute::MORPH_TANGENTS_1); - attributes.set(VertexAttribute::MORPH_TANGENTS_2); - attributes.set(VertexAttribute::MORPH_TANGENTS_3); } CodeGenerator::generateShaderInputs(vs, ShaderType::VERTEX, attributes, interpolation); @@ -247,6 +239,12 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, cg.generateUniforms(vs, ShaderType::VERTEX, BindingPoints::PER_RENDERABLE_BONES, UibGenerator::getPerRenderableBonesUib()); + cg.generateUniforms(vs, ShaderType::VERTEX, + BindingPoints::PER_RENDERABLE_MORPHING, + UibGenerator::getPerRenderableMorphingUib()); + cg.generateSamplers(vs, + material.samplerBindings.getBlockOffset(BindingPoints::PER_RENDERABLE_MORPHING), + SibGenerator::getPerRenderPrimitiveMorphingSib(variantKey)); } cg.generateUniforms(vs, ShaderType::VERTEX, BindingPoints::PER_MATERIAL_INSTANCE, material.uib); diff --git a/libs/gltfio/include/gltfio/FilamentAsset.h b/libs/gltfio/include/gltfio/FilamentAsset.h index 87daa3c38e..2b0a65863b 100644 --- a/libs/gltfio/include/gltfio/FilamentAsset.h +++ b/libs/gltfio/include/gltfio/FilamentAsset.h @@ -203,6 +203,16 @@ public: */ Animator* getAnimator() noexcept; + /** + * Updates the morphing weights in the given entity. + */ + void setMorphWeights(utils::Entity entity, const float* weights, size_t count); + + /** + * Gets the number of morphing in the given entity. + */ + int getMorphTargetCount(utils::Entity entity) noexcept; + /** * Lazily creates a single LINES renderable that draws the transformed bounding-box hierarchy * for diagnostic purposes. The wireframe is owned by the asset so clients should not delete it. diff --git a/libs/gltfio/src/Animator.cpp b/libs/gltfio/src/Animator.cpp index 4879a637fc..0102f4c682 100644 --- a/libs/gltfio/src/Animator.cpp +++ b/libs/gltfio/src/Animator.cpp @@ -176,7 +176,6 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) { mImpl->instance = instance; mImpl->renderableManager = &asset->mEngine->getRenderableManager(); mImpl->transformManager = &asset->mEngine->getTransformManager(); - mImpl->morpher = new MorphHelper(asset, instance); const cgltf_data* srcAsset = asset->mSourceAsset->hierarchy; const cgltf_animation* srcAnims = srcAsset->animations; @@ -222,6 +221,8 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) { } } } + + mImpl->morpher = asset->mMorpher; } void Animator::addInstance(FFilamentInstance* instance) { @@ -235,7 +236,6 @@ void Animator::addInstance(FFilamentInstance* instance) { } Animator::~Animator() { - delete mImpl->morpher; delete mImpl; } @@ -461,7 +461,7 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn } } - morpher->applyWeights(channel.targetEntity, weights.data(), weights.size()); + morpher->setWeights(channel.targetEntity, weights.data(), weights.size()); return; } } diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 13ef84b3c3..6a855aa2b0 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -520,14 +521,15 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const // node weights are provided, they override the ones specified on the mesh. if (numMorphTargets > 0) { RenderableManager::Instance renderable = mRenderableManager.getInstance(entity); - float4 weights(0, 0, 0, 0); - for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, mesh->weights_count); ++i) { + const auto size = std::min(MAX_MORPH_TARGETS, std::max(mesh->weights_count, node->weights_count)); + std::vector weights(size); + for (cgltf_size i = 0, c = std::min(size, mesh->weights_count); i < c; ++i) { weights[i] = mesh->weights[i]; } - for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, node->weights_count); ++i) { + for (cgltf_size i = 0, c = std::min(size, node->weights_count); i < c; ++i) { weights[i] = node->weights[i]; } - mRenderableManager.setMorphWeights(renderable, weights); + mRenderableManager.setMorphWeights(renderable, weights.data(), size); } } @@ -688,15 +690,11 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out cgltf_size targetsCount = inPrim->targets_count; - // There is no need to emit a warning if there are more than 4 targets. This is only the base - // VertexBuffer and more might be created by MorphHelper. if (targetsCount > MAX_MORPH_TARGETS) { + utils::slog.w << "WARNING: Exceeded max morph target count of " << MAX_MORPH_TARGETS << utils::io::endl; targetsCount = MAX_MORPH_TARGETS; } - constexpr int baseTangentsAttr = (int) VertexAttribute::MORPH_TANGENTS_0; - constexpr int basePositionAttr = (int) VertexAttribute::MORPH_POSITION_0; - for (cgltf_size targetIndex = 0; targetIndex < targetsCount; targetIndex++) { const cgltf_morph_target& morphTarget = inPrim->targets[targetIndex]; for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) { @@ -705,17 +703,8 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out const cgltf_attribute_type atype = attribute.type; const int morphId = targetIndex + 1; - // The glTF tangent data is ignored here, but honored in ResourceLoader. - if (atype == cgltf_attribute_type_tangent) { - continue; - } - - if (atype == cgltf_attribute_type_normal) { - VertexAttribute attr = (VertexAttribute) (baseTangentsAttr + targetIndex); - vbb.attribute(attr, slot, VertexBuffer::AttributeType::SHORT4); - vbb.normalized(attr); - outPrim->morphTangents[targetIndex] = slot; - addBufferSlot({&mResult->mGenerateTangents, atype, slot++, morphId}); + // The glTF normal and tangent data are ignored here, but honored in ResourceLoader. + if (atype == cgltf_attribute_type_normal || atype == cgltf_attribute_type_tangent) { continue; } @@ -736,13 +725,6 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out slog.e << "Unsupported accessor type in " << name << io::endl; return false; } - const int stride = (fatype == actualType) ? accessor->stride : 0; - - VertexAttribute attr = (VertexAttribute) (basePositionAttr + targetIndex); - vbb.attribute(attr, slot, fatype, 0, stride); - vbb.normalized(attr, accessor->normalized); - outPrim->morphPositions[targetIndex] = slot; - addBufferSlot({accessor, atype, slot++, morphId}); } } diff --git a/libs/gltfio/src/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index c548d20bfc..a4061fe524 100644 --- a/libs/gltfio/src/FFilamentAsset.h +++ b/libs/gltfio/src/FFilamentAsset.h @@ -65,13 +65,13 @@ namespace gltfio { class Animator; class Wireframe; +class MorphHelper; // Encapsulates VertexBuffer::setBufferAt() or IndexBuffer::setBuffer(). struct BufferSlot { const cgltf_accessor* accessor; cgltf_attribute_type attribute; int bufferIndex; // for vertex buffers only - int morphTarget; // 0 if no morphing, otherwise 1-based index filament::VertexBuffer* vertexBuffer; filament::IndexBuffer* indexBuffer; }; @@ -97,8 +97,6 @@ struct Primitive { filament::IndexBuffer* indices = nullptr; filament::Aabb aabb; // object-space bounding box UvMap uvmap; // mapping from each glTF UV set to either UV0 or UV1 (8 bytes) - uint8_t morphPositions[4] = {}; // Buffer indices for MORPH_POSITION_0, MORPH_POSITION_1 etc. - uint8_t morphTangents[4] = {}; // Buffer indices for MORPH_TANGENTS_0, MORPH_TANGENTS_1, etc. }; using MeshCache = tsl::robin_map>; @@ -194,6 +192,10 @@ struct FFilamentAsset : public FilamentAsset { Animator* getAnimator() noexcept; + void setMorphWeights(utils::Entity entity , const float* weights, size_t count) noexcept; + + int getMorphTargetCount(utils::Entity entity) noexcept; + utils::Entity getWireframe() noexcept; filament::Engine* getEngine() const noexcept { @@ -243,6 +245,7 @@ struct FFilamentAsset : public FilamentAsset { std::vector mInstances; SkinVector mSkins; // unused for instanced assets Animator* mAnimator = nullptr; + MorphHelper* mMorpher = nullptr; Wireframe* mWireframe = nullptr; bool mResourcesLoaded = false; DependencyGraph mDependencyGraph; diff --git a/libs/gltfio/src/FilamentAsset.cpp b/libs/gltfio/src/FilamentAsset.cpp index ed6a9a25ef..843a5aec2b 100644 --- a/libs/gltfio/src/FilamentAsset.cpp +++ b/libs/gltfio/src/FilamentAsset.cpp @@ -22,6 +22,7 @@ #include #include +#include "MorphHelper.h" #include "Wireframe.h" using namespace filament; @@ -41,6 +42,7 @@ FFilamentAsset::~FFilamentAsset() { delete mAnimator; delete mWireframe; + delete mMorpher; mEngine->destroy(mRoot); mEntityManager->destroy(mRoot); @@ -95,6 +97,19 @@ Animator* FFilamentAsset::getAnimator() noexcept { return mAnimator; } +void FFilamentAsset::setMorphWeights(utils::Entity entity, const float* weights, size_t count) noexcept { + if (mResourcesLoaded) { + mMorpher->setWeights(entity, weights, count); + } +} + +int FFilamentAsset::getMorphTargetCount(utils::Entity entity) noexcept { + if (mResourcesLoaded) { + return mMorpher->getTargetCount(entity); + } + return 0; +} + Entity FFilamentAsset::getWireframe() noexcept { if (!mWireframe) { mWireframe = new Wireframe(this); @@ -274,6 +289,14 @@ Animator* FilamentAsset::getAnimator() noexcept { return upcast(this)->getAnimator(); } +void FilamentAsset::setMorphWeights(utils::Entity entity, const float* weights, size_t count) { + return upcast(this)->setMorphWeights(entity, weights, count); +} + +int FilamentAsset::getMorphTargetCount(utils::Entity entity) noexcept { + return upcast(this)->getMorphTargetCount(entity); +} + Entity FilamentAsset::getWireframe() noexcept { return upcast(this)->getWireframe(); } diff --git a/libs/gltfio/src/MorphHelper.cpp b/libs/gltfio/src/MorphHelper.cpp index b2680607f7..abad68525c 100644 --- a/libs/gltfio/src/MorphHelper.cpp +++ b/libs/gltfio/src/MorphHelper.cpp @@ -16,11 +16,12 @@ #include "MorphHelper.h" -#include #include #include +#include +#include +#include -#include "GltfEnums.h" #include "TangentsJob.h" using namespace filament; @@ -45,14 +46,6 @@ static bool isSubsetOf(ubyte4 a, ubyte4 b) { return true; } -static int indexOf(int a, ubyte4 b) { - if (a == b.x) return 0; - if (a == b.y) return 1; - if (a == b.z) return 2; - if (a == b.w) return 3; - return -1; -} - MorphHelper::MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst) : mAsset(asset), mInstance(inst) { NodeMap& sourceNodes = asset->isInstanced() ? asset->mInstances[0]->nodeMap : asset->mNodeMap; @@ -62,7 +55,7 @@ MorphHelper::MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst) : mAsse if (mesh) { cgltf_primitive const* prims = mesh->primitives; for (cgltf_size pi = 0, count = mesh->primitives_count; pi < count; ++pi) { - addPrimitive(mesh, pi, &mMorphTable[pair.second]); + addPrimitive(mesh, pi, pair.second); } } } @@ -72,96 +65,50 @@ MorphHelper::~MorphHelper() { auto engine = mAsset->mEngine; for (auto& entry : mMorphTable) { for (auto& prim : entry.second.primitives) { - for (auto& target : prim.targets) { - engine->destroy(target.bufferObject); + if (prim.targets) { + engine->destroy(prim.targets); } } } } -void MorphHelper::applyWeights(Entity entity, float const* weights, size_t count) noexcept { +void MorphHelper::setWeights(Entity entity, float const* weights, int count) noexcept { auto& engine = *mAsset->mEngine; - auto renderableManager = &engine.getRenderableManager(); - auto renderable = renderableManager->getInstance(entity); + auto& rcm = engine.getRenderableManager(); + rcm.setMorphWeights(rcm.getInstance(entity), weights, count); +} - // If there are 4 or fewer targets, we can simply re-use the original VertexBuffer. - if (count <= 4) { - float4 vec{}; - for (size_t i = 0; i < count; i++) { - vec[i] = weights[i]; - } - renderableManager->setMorphWeights(renderable, vec); - return; - } - - // We honor up to 255 weights because our set representation is a 4-tuple of bytes, with one - // slot reserved for a sentinel value. Note that 255 is much more than the glTF min spec of 4. - count = std::min(count, size_t(255)); - - // Make a copy of the weights because we want to re-order them. - auto& sorted = mPartiallySortedWeights; - sorted.clear(); - sorted.insert(sorted.begin(), weights, weights + count); - - // Find the four highest weights in O(n) by doing a partial sort. - std::nth_element(sorted.begin(), sorted.begin() + 4, sorted.end(), [](float a, float b) { - return a > b; - }); - - // Find the "primary indices" which are the indices of the four highest weights. This is O(n). - ubyte4 primaryIndices = {kUnused, kUnused, kUnused, kUnused}; - for (size_t index = 0, primary = 0; index < count && primary < 4; ++index) { - const float w = weights[index]; - if (w > 0 && (w == sorted[0] || w == sorted[1] || w == sorted[2] || w == sorted[3])) { - primaryIndices[primary++] = index; +int MorphHelper::getTargetCount(Entity entity) const noexcept { + if (mMorphTable.count(entity)) { + auto& primitive = mMorphTable.at(entity).primitives; + if (!primitive.empty() && primitive[0].targets) { + return primitive[0].targets->getCount(); } } - - // Swap out the buffer objects for the primary indices. - for (const auto& prim : mMorphTable[entity].primitives) { - for (const auto& target : prim.targets) { - const int index = indexOf(target.morphTargetIndex, primaryIndices); - if (index > -1) { - assert_invariant(primaryIndices[index] == target.morphTargetIndex); - VertexBuffer* vb = prim.vertexBuffer; - const int bufferObjectSlot = target.type == cgltf_attribute_type_position ? - prim.positions[index] : prim.tangents[index]; - - // Slot 0 is always used for the base position so if it's getting clobbered, then - // something is seriously wrong. Should never occur, assert just in case. - assert_invariant(bufferObjectSlot != 0); - - vb->setBufferObjectAt(engine, bufferObjectSlot, target.bufferObject); - - // Do not break out early because there could be more than one target entry for this - // particular target index (e.g. positions + tangents). - } - } - } - - // Finally, set the 4-tuple uniform for the weight values by derefing the primary indices. - // Note that we first create a "safe set" by replacing the unused sentinel with zero. - float4 highest; - for (int i = 0; i < 4; i++) { - highest[i] = (primaryIndices[i] == kUnused) ? 0 : weights[primaryIndices[i]]; - } - renderableManager->setMorphWeights(renderable, highest); + return 0; } // This method copies various morphing-related data from the FilamentAsset MeshCache primitive // (which lives in transient memory) into the MorphHelper primitive (which will stay resident). -void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, TableEntry* entry) { +void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Entity entity){ + auto& entry = mMorphTable[entity]; auto& engine = *mAsset->mEngine; const cgltf_primitive& prim = mesh->primitives[primitiveIndex]; const auto& gltfioPrim = mAsset->mMeshCache.at(mesh)[primitiveIndex]; VertexBuffer* vertexBuffer = gltfioPrim.vertices; + entry.primitives.push_back({ nullptr }); - entry->primitives.push_back({ vertexBuffer }); - auto& morphHelperPrim = entry->primitives.back(); + auto& morphHelperPrim = entry.primitives.back(); - for (int i = 0; i < 4; i++) { - morphHelperPrim.positions[i] = gltfioPrim.morphPositions[i]; - morphHelperPrim.tangents[i] = gltfioPrim.morphTangents[i]; + if (prim.targets_count) { + morphHelperPrim.targets = MorphTargetBuffer::Builder() + .vertexCount(vertexBuffer->getVertexCount()) + .count(prim.targets_count) + .build(engine); + + auto& rcm = engine.getRenderableManager(); + rcm.setMorphTargetBufferAt(rcm.getInstance(entity), + primitiveIndex, morphHelperPrim.targets); } const cgltf_accessor* previous = nullptr; @@ -175,19 +122,15 @@ void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Table continue; } if (atype == cgltf_attribute_type_normal) { - // TODO: use JobSystem for this, like what we do for non-morph tangents. TangentsJob job; TangentsJob::Params params = { .in = { &prim, targetIndex } }; TangentsJob::run(¶ms); if (params.out.results) { - const size_t size = params.out.vertexCount * sizeof(short4); - BufferObject* bufferObject = BufferObject::Builder().size(size).build(engine); - VertexBuffer::BufferDescriptor bd(params.out.results, size, FREE_CALLBACK); - bufferObject->setBuffer(engine, std::move(bd)); - params.out.results = nullptr; - morphHelperPrim.targets.push_back({bufferObject, targetIndex, atype}); + morphHelperPrim.targets->setTangentsAt(engine, targetIndex, + params.out.results, params.out.vertexCount); + free(params.out.results); } continue; } @@ -201,19 +144,16 @@ void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Table if (accessor->buffer_view) { auto bufferData = (const uint8_t*) accessor->buffer_view->buffer->data; assert_invariant(bufferData); - const uint8_t* data = computeBindingOffset(accessor) + bufferData; - const uint32_t size = computeBindingSize(accessor); + auto* data = computeBindingOffset(accessor) + bufferData; - // This creates a copy because we don't know when the user will free the cgltf - // source data. For non-morphed vertex buffers, we use a sharing mechanism to - // prevent copies, but here we just want to keep it as simple as possible. - uint8_t* clone = (uint8_t*) malloc(size); - memcpy(clone, data, size); - - BufferObject* bufferObject = BufferObject::Builder().size(size).build(engine); - VertexBuffer::BufferDescriptor bd(clone, size, FREE_CALLBACK); - bufferObject->setBuffer(engine, std::move(bd)); - morphHelperPrim.targets.push_back({bufferObject, targetIndex, atype}); + if (accessor->type == cgltf_type_vec3) { + morphHelperPrim.targets->setPositionsAt(engine, targetIndex, + (const float3*) data, vertexBuffer->getVertexCount()); + } else { + assert_invariant(accessor->type == cgltf_type_vec4); + morphHelperPrim.targets->setPositionsAt(engine, targetIndex, + (const float4*) data, vertexBuffer->getVertexCount()); + } } } } diff --git a/libs/gltfio/src/MorphHelper.h b/libs/gltfio/src/MorphHelper.h index 18f9a6fe48..45c51ba06f 100644 --- a/libs/gltfio/src/MorphHelper.h +++ b/libs/gltfio/src/MorphHelper.h @@ -14,9 +14,14 @@ * limitations under the License. */ +#ifndef GLTFIO_MORPHHELPER_H +#define GLTFIO_MORPHHELPER_H + #include "FFilamentAsset.h" #include "FFilamentInstance.h" +#include + #include #include @@ -44,35 +49,25 @@ public: MorphHelper(FFilamentAsset* asset, FFilamentInstance* inst); ~MorphHelper(); - /** - * Picks the 4 most influential weights and applies them to the target entity. - */ - void applyWeights(Entity targetEntity, float const* weights, size_t count) noexcept; + void setWeights(Entity entity, float const* weights, int count) noexcept; + int getTargetCount(Entity entity) const noexcept; private: - struct GltfTarget { - filament::BufferObject* bufferObject; - int morphTargetIndex; - cgltf_attribute_type type; - }; - struct GltfPrimitive { - filament::VertexBuffer* vertexBuffer; - uint8_t positions[4]; - uint8_t tangents[4]; - std::vector targets; // TODO: flatten this? + filament::MorphTargetBuffer* targets; }; struct TableEntry { std::vector primitives; // TODO: flatten this? }; - void addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, TableEntry* entry); + void addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Entity entity); - std::vector mPartiallySortedWeights; tsl::robin_map mMorphTable; const FFilamentAsset* mAsset; const FFilamentInstance* mInstance; }; } // namespace gltfio + +#endif // GLTFIO_MORPHHELPER_H diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 4d7142b9dd..e98785f205 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -20,6 +20,7 @@ #include "GltfEnums.h" #include "FFilamentAsset.h" #include "TangentsJob.h" +#include "MorphHelper.h" #include "upcast.h" #include @@ -872,15 +873,10 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { // Collect all TANGENT vertex attribute slots that need to be populated. tsl::robin_map baseTangents; - tsl::robin_map morphTangents[4]; for (auto slot : asset->mBufferSlots) { if (slot.accessor != kGenerateTangents && slot.accessor != kGenerateNormals) { continue; } - if (slot.morphTarget) { - morphTangents[slot.morphTarget - 1][slot.vertexBuffer] = slot.bufferIndex; - continue; - } baseTangents[slot.vertexBuffer] = slot.bufferIndex; } @@ -896,13 +892,6 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { if (iter != baseTangents.end()) { jobParams.emplace_back(Params {{ pair.first }, {vb, iter->second }}); } - for (int morphTarget = 0; morphTarget < 4; morphTarget++) { - const auto& tangents = morphTangents[morphTarget]; - auto iter = tangents.find(vb); - if (iter != tangents.end()) { - jobParams.emplace_back(Params {{ pair.first, morphTarget }, {vb, iter->second }}); - } - } } // Kick off jobs for computing tangent frames. @@ -923,6 +912,8 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) { params.out.results, bo->getByteCount(), FREE_CALLBACK)); params.context.vb->setBufferObjectAt(*mEngine, params.context.slot, bo); } + + asset->mMorpher = new MorphHelper(asset, nullptr); } ResourceLoader::Impl::~Impl() { diff --git a/libs/viewer/include/viewer/SimpleViewer.h b/libs/viewer/include/viewer/SimpleViewer.h index f3787f2d8f..859942e231 100644 --- a/libs/viewer/include/viewer/SimpleViewer.h +++ b/libs/viewer/include/viewer/SimpleViewer.h @@ -35,6 +35,8 @@ #include #include +#include + namespace filagui { class ImGuiHelper; } @@ -241,6 +243,7 @@ private: Settings mSettings; int mSidebarWidth; uint32_t mFlags; + std::vector mMorphWeights; // 0 is the default "free camera". Additional cameras come from the gltf file (1-based index). int mCurrentCamera = 0; diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index f3feb2ad30..65613e17b6 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -34,6 +34,7 @@ #include #include +#include using namespace filagui; using namespace filament::math; @@ -406,6 +407,13 @@ void SimpleViewer::populateScene(FilamentAsset* asset, FilamentInstance* instan mAnimator = instanceToAnimate ? instanceToAnimate->getAnimator() : asset->getAnimator(); updateRootTransform(); mScene->addEntities(asset->getLightEntities(), asset->getLightEntityCount()); + + int morphCount = 0; + for (size_t i = 0, c = asset->getEntityCount(); i < c; ++i) { + auto entity = asset->getEntities()[i]; + morphCount = std::max(morphCount, asset->getMorphTargetCount(entity)); + } + mMorphWeights.resize(std::min(morphCount, 128), 0); } auto& tcm = mEngine->getRenderableManager(); @@ -427,6 +435,7 @@ void SimpleViewer::removeAsset() { } mAsset = nullptr; mAnimator = nullptr; + mMorphWeights.clear(); } void SimpleViewer::setIndirectLight(filament::IndirectLight* ibl, @@ -967,6 +976,19 @@ void SimpleViewer::updateUserInterface() { ImGui::Unindent(); } + if (!mMorphWeights.empty() && ImGui::CollapsingHeader("Morphing")) { + for (int i = 0; i != mMorphWeights.size(); ++i) { + std::stringstream ss; + ss << i + 1 << " Weight"; + std::string label = ss.str(); + ImGui::SliderFloat(label.data(), &mMorphWeights[i], 0.0f, 1.0); + } + for (size_t i = 0, c = mAsset->getEntityCount(); i != c; ++i) { + mAsset->setMorphWeights(mAsset->getEntities()[i], + mMorphWeights.data(), mMorphWeights.size()); + } + } + if (mEnableWireframe) { mScene->addEntity(mAsset->getWireframe()); } else { diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index 4ba481a8da..cde22bfee9 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -20,6 +20,15 @@ mat3 getWorldFromModelNormalMatrix() { // Attributes access //------------------------------------------------------------------------------ +/** @public-api */ +int getVertexIndex() { +#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) + return gl_VertexIndex; +#else + return gl_VertexID; +#endif +} + #if defined(HAS_SKINNING_OR_MORPHING) vec3 mulBoneNormal(vec3 n, uint i) { @@ -63,6 +72,26 @@ void skinPosition(inout vec3 p, const uvec4 ids, const vec4 weights) { + mulBoneVertex(p, ids.z) * weights.z + mulBoneVertex(p, ids.w) * weights.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); + for (uint i = 0u; i < objectUniforms.morphTargetCount; ++i) { + texcoord.z = int(i) * 2 + 0; + p += morphingUniforms.weights[i] * texelFetch(morphing_targets, texcoord, 0); + } +} + +void morphNormal(inout vec3 n) { + ivec3 texcoord = ivec3(getVertexIndex() % MAX_MORPH_TARGET_BUFFER_WIDTH, getVertexIndex() / MAX_MORPH_TARGET_BUFFER_WIDTH, 0); + for (uint i = 0u; i < objectUniforms.morphTargetCount; ++i) { + texcoord.z = int(i) * 2 + 1; + vec3 normal; + toTangentFrame(texelFetch(morphing_targets, texcoord, 0), normal); + n += morphingUniforms.weights[i].xyz * normal; + } +} #endif /** @public-api */ @@ -72,10 +101,7 @@ vec4 getPosition() { #if defined(HAS_SKINNING_OR_MORPHING) if ((objectUniforms.flags & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0u) { - pos += objectUniforms.morphWeights.x * mesh_custom0; - pos += objectUniforms.morphWeights.y * mesh_custom1; - pos += objectUniforms.morphWeights.z * mesh_custom2; - pos += objectUniforms.morphWeights.w * mesh_custom3; + morphPosition(pos); } if ((objectUniforms.flags & FILAMENT_OBJECT_SKINNING_ENABLED_BIT) != 0u) { @@ -112,15 +138,6 @@ vec4 getCustom6() { return mesh_custom6; } vec4 getCustom7() { return mesh_custom7; } #endif -/** @public-api */ -int getVertexIndex() { -#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) - return gl_VertexIndex; -#else - return gl_VertexID; -#endif -} - //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 32637d1c71..42b652a354 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -36,15 +36,7 @@ void main() { #if defined(HAS_SKINNING_OR_MORPHING) if ((objectUniforms.flags & FILAMENT_OBJECT_MORPHING_ENABLED_BIT) != 0u) { - vec3 normal0, normal1, normal2, normal3; - toTangentFrame(mesh_custom4, normal0); - toTangentFrame(mesh_custom5, normal1); - toTangentFrame(mesh_custom6, normal2); - toTangentFrame(mesh_custom7, normal3); - material.worldNormal += objectUniforms.morphWeights.x * normal0; - material.worldNormal += objectUniforms.morphWeights.y * normal1; - material.worldNormal += objectUniforms.morphWeights.z * normal2; - material.worldNormal += objectUniforms.morphWeights.w * normal3; + morphNormal(material.worldNormal); material.worldNormal = normalize(material.worldNormal); } diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 9163ca7c7c..46e6b3ee55 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -992,14 +992,6 @@ export enum VertexAttribute { CUSTOM5 = 13, CUSTOM6 = 14, CUSTOM7 = 15, - MORPH_POSITION_0 = CUSTOM0, - MORPH_POSITION_1 = CUSTOM1, - MORPH_POSITION_2 = CUSTOM2, - MORPH_POSITION_3 = CUSTOM3, - MORPH_TANGENTS_0 = CUSTOM4, - MORPH_TANGENTS_1 = CUSTOM5, - MORPH_TANGENTS_2 = CUSTOM6, - MORPH_TANGENTS_3 = CUSTOM7, } export enum VertexBuffer$AttributeType { diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 1c29b48897..6a54edcc54 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1045,11 +1045,10 @@ class_("RenderableManager") } self->setBones(instance, bones.data(), bones.size(), offset); }), allow_raw_pointers()) - - // NOTE: this cannot take a float4 due to a binding issue. + .function("setMorphWeights", EMBIND_LAMBDA(void, (RenderableManager* self, - RenderableManager::Instance instance, float x, float y, float z, float w), { - self->setMorphWeights(instance, {x, y, z, w}); + RenderableManager::Instance instance, float* weights, size_t count), { + self->setMorphWeights(instance, weights, count); }), allow_raw_pointers()) .function("getAxisAlignedBoundingBox", &RenderableManager::getAxisAlignedBoundingBox) diff --git a/web/filament-js/jsenums.cpp b/web/filament-js/jsenums.cpp index ed9d16fe55..45647ba262 100644 --- a/web/filament-js/jsenums.cpp +++ b/web/filament-js/jsenums.cpp @@ -61,15 +61,7 @@ enum_("VertexAttribute") .value("CUSTOM4", CUSTOM4) .value("CUSTOM5", CUSTOM5) .value("CUSTOM6", CUSTOM6) - .value("CUSTOM7", CUSTOM7) - .value("MORPH_POSITION_0", MORPH_POSITION_0) - .value("MORPH_POSITION_1", MORPH_POSITION_1) - .value("MORPH_POSITION_2", MORPH_POSITION_2) - .value("MORPH_POSITION_3", MORPH_POSITION_3) - .value("MORPH_TANGENTS_0", MORPH_TANGENTS_0) - .value("MORPH_TANGENTS_1", MORPH_TANGENTS_1) - .value("MORPH_TANGENTS_2", MORPH_TANGENTS_2) - .value("MORPH_TANGENTS_3", MORPH_TANGENTS_3); + .value("CUSTOM7", CUSTOM7); enum_("BufferObject$BindingType") .value("VERTEX", BufferObject::BindingType::VERTEX);