From 1952f0a1f786858ea6c853e1e01ed36c703acad6 Mon Sep 17 00:00:00 2001 From: daemyung jang Date: Wed, 26 Jan 2022 14:57:11 +0900 Subject: [PATCH] Change morphing APIs to support LOD (#5080) * Change morphing APIs to support LOD * Add Java and Web bindings for morphing * Omit and fix JS bindings * Fix styles in MorphTargetBuffer.java --- android/filament-android/CMakeLists.txt | 1 + .../src/main/cpp/MorphTargetBuffer.cpp | 114 +++++++++++ .../src/main/cpp/RenderableManager.cpp | 11 ++ .../android/filament/MorphTargetBuffer.java | 177 ++++++++++++++++++ .../android/filament/RenderableManager.java | 34 ++++ filament/include/filament/RenderableManager.h | 6 +- filament/src/components/RenderableManager.cpp | 21 ++- filament/src/components/RenderableManager.h | 4 +- libs/gltfio/src/MorphHelper.cpp | 4 +- web/filament-js/jsbindings.cpp | 9 +- 10 files changed, 367 insertions(+), 14 deletions(-) create mode 100644 android/filament-android/src/main/cpp/MorphTargetBuffer.cpp create mode 100644 android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java diff --git a/android/filament-android/CMakeLists.txt b/android/filament-android/CMakeLists.txt index df4ea3cbdc..f2a12683ff 100644 --- a/android/filament-android/CMakeLists.txt +++ b/android/filament-android/CMakeLists.txt @@ -74,6 +74,7 @@ add_library(filament-jni SHARED src/main/cpp/Material.cpp src/main/cpp/MaterialInstance.cpp src/main/cpp/MathUtils.cpp + src/main/cpp/MorphTargetBuffer.cpp src/main/cpp/RenderableManager.cpp src/main/cpp/Renderer.cpp src/main/cpp/RenderTarget.cpp diff --git a/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp b/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp new file mode 100644 index 0000000000..893a9216a5 --- /dev/null +++ b/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp @@ -0,0 +1,114 @@ +/* + * 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 + +#include +#include +#include + +#include + +#include "common/CallbackUtils.h" +#include "common/NioUtils.h" + +using namespace filament; +using namespace backend; + +extern "C" +JNIEXPORT jlong JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nCreateBuilder(JNIEnv*, jclass) { + return (jlong) new MorphTargetBuffer::Builder(); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nDestroyBuilder(JNIEnv*, jclass, + jlong nativeBuilder) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder *) nativeBuilder; + delete builder; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nBuilderVertexCount(JNIEnv*, jclass, + jlong nativeBuilder, jint vertexCount) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder *) nativeBuilder; + builder->vertexCount((size_t) vertexCount); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nBuilderCount(JNIEnv*, jclass, + jlong nativeBuilder, jint count) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder *) nativeBuilder; + builder->count((size_t) count); +} + +extern "C" +JNIEXPORT jlong JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass, + jlong nativeBuilder, jlong nativeEngine) { + MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder *) nativeBuilder; + Engine *engine = (Engine *) nativeEngine; + return (jlong) builder->build(*engine); +} + +// ------------------------------------------------------------------------------------------------ + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nSetPositionsAt(JNIEnv* env, jclass, + jlong nativeObject, jlong nativeEngine, + jint targetIndex, jfloatArray positions, jint count) { + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; + Engine *engine = (Engine *) nativeEngine; + jfloat* data = env->GetFloatArrayElements(positions, NULL); + morphTargetBuffer->setPositionsAt(*engine, targetIndex, + (math::float4*) data, size_t(count)); + env->ReleaseFloatArrayElements(positions, data, JNI_ABORT); + return 0; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nSetTangentsAt(JNIEnv* env, jclass, + jlong nativeObject, jlong nativeEngine, + jint targetIndex, jshortArray tangents, jint count) { + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; + Engine *engine = (Engine *) nativeEngine; + jshort* data = env->GetShortArrayElements(tangents, NULL); + morphTargetBuffer->setTangentsAt(*engine, targetIndex, + (math::short4*) data, size_t(count)); + env->ReleaseShortArrayElements(tangents, data, JNI_ABORT); + return 0; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nGetVertexCount(JNIEnv*, jclass, + jlong nativeObject) { + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; + return (jint)morphTargetBuffer->getVertexCount(); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_MorphTargetBuffer_nGetCount(JNIEnv*, jclass, + jlong nativeObject) { + MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; + return (jint)morphTargetBuffer->getCount(); +} diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index 8d3d43dcdd..493996696a 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -275,6 +275,17 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env, env->ReleaseFloatArrayElements(weights, vec, JNI_ABORT); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEnv*, + 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); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nSetAxisAlignedBoundingBox(JNIEnv*, jclass, jlong nativeRenderableManager, jint i, jfloat cx, jfloat cy, jfloat cz, diff --git a/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java new file mode 100644 index 0000000000..5376b9ad16 --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.filament; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; + +import java.nio.Buffer; +import java.nio.BufferOverflowException; +import java.nio.FloatBuffer; + +public class MorphTargetBuffer { + private long mNativeObject; + private MorphTargetBuffer(long nativeMorphTargetBuffer) { + mNativeObject = nativeMorphTargetBuffer; + } + + public static class Builder { + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + // Keep to finalize native resources + private final MorphTargetBuffer.Builder.BuilderFinalizer mFinalizer; + private final long mNativeBuilder; + + public Builder() { + mNativeBuilder = nCreateBuilder(); + mFinalizer = new MorphTargetBuffer.Builder.BuilderFinalizer(mNativeBuilder); + } + + /** + * Size of the morph targets in vertex counts. + * + * @param vertexCount Number of vertex counts the morph targets can hold. + * @return this Builder object for chaining calls + */ + @NonNull + public Builder vertexCount(@IntRange(from = 1) int vertexCount) { + nBuilderVertexCount(mNativeBuilder, vertexCount); + return this; + } + + /** + * Size of the morph targets in targets. + * + * @param count Number of targets the morph targets can hold. + * @return this Builder object for chaining calls + */ + @NonNull + public Builder count(@IntRange(from = 1) int count) { + nBuilderCount(mNativeBuilder, count); + return this; + } + + /** + * Creates and returns the MorphTargetBuffer object. + * + * @param engine reference to the {@link Engine} to associate this MorphTargetBuffer + * with. + * + * @return the newly created MorphTargetBuffer object + * + * @exception IllegalStateException if the MorphTargetBuffer could not be created + * + * @see #setMorphTargetBufferAt + */ + @NonNull + public MorphTargetBuffer build(@NonNull Engine engine) { + long nativeMorphTargetBuffer = nBuilderBuild(mNativeBuilder, engine.getNativeObject()); + if (nativeMorphTargetBuffer == 0) + throw new IllegalStateException("Couldn't create MorphTargetBuffer"); + return new MorphTargetBuffer(nativeMorphTargetBuffer); + } + + private static class BuilderFinalizer { + private final long mNativeObject; + + BuilderFinalizer(long nativeObject) { + mNativeObject = nativeObject; + } + + @Override + public void finalize() { + try { + super.finalize(); + } catch (Throwable t) { // Ignore + } finally { + nDestroyBuilder(mNativeObject); + } + } + } + } + + /** + * Updates positions of morph target at the index. + * + * @param engine {@link Engine} instance + * @param targetIndex The index of morph target to be updated + * @param positions Pointer to at least count positions + * @param count Number of position elements in positions + */ + public void setPositionsAt(@NonNull Engine engine, + @IntRange(from = 0) int targetIndex, + @NonNull float[] positions, @IntRange(from = 0, to = 125) int count) { + int result = nSetPositionsAt(mNativeObject, engine.getNativeObject(), targetIndex, + positions, count); + if (result < 0) { + throw new BufferOverflowException(); + } + } + + /** + * Updates tangents of morph target at the index. + * + * @param engine {@link Engine} instance + * @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 + */ + public void setTangentsAt(@NonNull Engine engine, + @IntRange(from = 0) int targetIndex, + @NonNull short[] tangents, @IntRange(from = 0, to = 125) int count) { + int result = nSetTangentsAt(mNativeObject, engine.getNativeObject(), targetIndex, + tangents, count); + if (result < 0) { + throw new BufferOverflowException(); + } + } + + /** + * @return number of vertex count in this {@link MorphTargetBuffer} + */ + public int getVertexCount() { + return nGetVertexCount(mNativeObject); + } + + /** + * @return number of target count in this {@link MorphTargetBuffer} + */ + public int getCount() { + return nGetCount(mNativeObject); + } + + public long getNativeObject() { + if (mNativeObject == 0) { + throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer"); + } + return mNativeObject; + } + + void clearNativeObject() { + mNativeObject = 0; + } + + private static native long nCreateBuilder(); + private static native void nDestroyBuilder(long nativeBuilder); + private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount); + private static native void nBuilderCount(long nativeBuilder, int count); + private static native long nBuilderBuild(long nativeBuilder, long nativeEngine); + + private static native int nSetPositionsAt(long nativeObject, long nativeEngine, int targetIndex, float[] positions, int count); + private static native int nSetTangentsAt(long nativeObject, long nativeEngine, int targetIndex, short[] tangents, int count); + private static native int nGetVertexCount(long nativeObject); + private static native int nGetCount(long nativeObject); +} 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 1a5785ebe7..61291f038b 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 @@ -502,6 +502,39 @@ public class RenderableManager { nSetMorphWeights(mNativeObject, i, weights); } + /** + * Changes the morph target buffer for the given primitive. + * + *

The renderable must be built with morphing enabled.

+ * + * @see Builder#morphing + */ + public void setMorphTargetBufferAt(@EntityInstance int i, + @IntRange(from = 0) int level, + @IntRange(from = 0) int primitiveIndex, + @NonNull MorphTargetBuffer morphTargetBuffer, + @IntRange(from = 0) int offset, + @IntRange(from = 0) int count) { + nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, + morphTargetBuffer.getNativeObject(), offset, count); + } + + /** + * Changes the morph target buffer for the given primitive. + * + *

The renderable must be built with morphing enabled.

+ * + * @see Builder#morphing + */ + public void setMorphTargetBufferAt(@EntityInstance int i, + @IntRange(from = 0) int level, + @IntRange(from = 0) int primitiveIndex, + @NonNull MorphTargetBuffer morphTargetBuffer, + @IntRange(from = 0) int count) { + nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, + morphTargetBuffer.getNativeObject(), 0, count); + } + /** * Changes the bounding box used for frustum culling. * @@ -764,6 +797,7 @@ public class RenderableManager { private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset); private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset); private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights); + private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count); private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez); private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value); private static native void nSetPriority(long nativeRenderableManager, int i, int priority); diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 47f4fc56d4..5df4d183da 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -463,8 +463,10 @@ public: /** * Associates a MorphTargetBuffer to the given primitive. */ - void setMorphTargetBufferAt(Instance instance, - size_t primitiveIndex, MorphTargetBuffer* morphTargetBuffer) noexcept; + void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept; + void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* morphTargetBuffer, size_t count) noexcept; //!< \overload /** * Gets the bounding box used for frustum culling. diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 37ca07bc62..7ae6116433 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -609,10 +609,12 @@ void FRenderableManager::setMorphWeights(Instance instance, float const* weights } } -void FRenderableManager::setMorphTargetBufferAt(Instance instance, - size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer) noexcept { +void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept { + assert_invariant(offset == 0 && "Offset not yet supported."); + assert_invariant(count == morphTargetBuffer->getVertexCount() && "Count not yet supported."); if (instance) { - Slice& primitives = getRenderPrimitives(instance, 0); + Slice& primitives = getRenderPrimitives(instance, level); if (primitiveIndex < primitives.size()) { primitives[primitiveIndex].set(morphTargetBuffer); } @@ -753,9 +755,16 @@ void RenderableManager::setMorphWeights(Instance instance, float const* weights, 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::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept { + upcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, + upcast(morphTargetBuffer), offset, count); +} + +void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + MorphTargetBuffer* morphTargetBuffer, size_t count) noexcept { + upcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, + upcast(morphTargetBuffer), 0, count); } void RenderableManager::setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept { diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 38db7a3794..c557b7b299 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -111,8 +111,8 @@ public: 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; + void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex, + FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept; inline void setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept; inline bool getLightChannel(Instance instance, unsigned int channel) const noexcept; diff --git a/libs/gltfio/src/MorphHelper.cpp b/libs/gltfio/src/MorphHelper.cpp index deaf019cd6..9d9da60b66 100644 --- a/libs/gltfio/src/MorphHelper.cpp +++ b/libs/gltfio/src/MorphHelper.cpp @@ -112,8 +112,8 @@ void MorphHelper::addPrimitive(cgltf_mesh const* mesh, int primitiveIndex, Entit .build(engine); auto& rcm = engine.getRenderableManager(); - rcm.setMorphTargetBufferAt(rcm.getInstance(entity), - primitiveIndex, morphHelperPrim.targets); + rcm.setMorphTargetBufferAt(rcm.getInstance(entity), 0, primitiveIndex, + morphHelperPrim.targets, vertexBuffer->getVertexCount()); } const cgltf_accessor* previous = nullptr; diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 09b08b7b3f..cf5e4bd7fb 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1055,8 +1055,13 @@ class_("RenderableManager") }), allow_raw_pointers()) .function("setMorphWeights", EMBIND_LAMBDA(void, (RenderableManager* self, - RenderableManager::Instance instance, float* weights, size_t count), { - self->setMorphWeights(instance, weights, count); + RenderableManager::Instance instance, emscripten::val weights), { + auto nfloats = weights["length"].as(); + std::vector floats(nfloats); + for (size_t i = 0; i < nfloats; i++) { + floats[i] = weights[i].as(); + } + self->setMorphWeights(instance, floats.data(), nfloats); }), allow_raw_pointers()) .function("getAxisAlignedBoundingBox", &RenderableManager::getAxisAlignedBoundingBox)