From 3e5471d32a9669306706d8576ce1fa0bfaedadc5 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 14 Jul 2021 14:26:44 -0700 Subject: [PATCH] Implement sharing of bones for skinning The API now allows to create a standalone SkinningBuffer containing Bones for skinning. This SkinningBuffer can even be very large and contain bone data for several renderables. New APIs on RenderableManager allow to switch to skinning buffer mode and to set the SkinningBuffer (and window into it) to use. Fixes #3238 --- RELEASE_NOTES.md | 2 + android/filament-android/CMakeLists.txt | 1 + .../src/main/cpp/RenderableManager.cpp | 32 ++- .../src/main/cpp/SkinningBuffer.cpp | 116 +++++++++ .../android/filament/RenderableManager.java | 75 +++++- .../android/filament/SkinningBuffer.java | 177 ++++++++++++++ filament/CMakeLists.txt | 3 + filament/include/filament/RenderableManager.h | 39 +++ filament/include/filament/SkinningBuffer.h | 119 +++++++++ filament/src/Engine.cpp | 10 + filament/src/RenderPass.cpp | 22 +- filament/src/RenderPass.h | 13 +- filament/src/Scene.cpp | 26 +- filament/src/SkinningBuffer.cpp | 180 ++++++++++++++ filament/src/View.cpp | 4 - filament/src/components/RenderableManager.cpp | 227 ++++++++++-------- filament/src/components/RenderableManager.h | 45 ++-- filament/src/details/Engine.h | 4 + filament/src/details/Scene.h | 4 +- filament/src/details/SkinningBuffer.h | 80 ++++++ filament/test/filament_test.cpp | 4 +- 21 files changed, 1008 insertions(+), 175 deletions(-) create mode 100644 android/filament-android/src/main/cpp/SkinningBuffer.cpp create mode 100644 android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java create mode 100644 filament/include/filament/SkinningBuffer.h create mode 100644 filament/src/SkinningBuffer.cpp create mode 100644 filament/src/details/SkinningBuffer.h diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c430c39172..c398ad4c5a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,8 @@ A new header is inserted each time a *tag* is created. - engine: Luminance scaling can now be used with any tone mapping operator. It was previously tied to the "EVILS" tone mapping operator. - engine: Removed the "EVILS" tone mapping operator [⚠️ **API Change**]. +- engine: Improvements to Skinning. A new `SkinningBuffer` API allows bone sharing between + renderables. ## v1.11.0 diff --git a/android/filament-android/CMakeLists.txt b/android/filament-android/CMakeLists.txt index f9707c09d5..87e9fb8500 100644 --- a/android/filament-android/CMakeLists.txt +++ b/android/filament-android/CMakeLists.txt @@ -75,6 +75,7 @@ add_library(filament-jni SHARED src/main/cpp/RenderTarget.cpp src/main/cpp/Scene.cpp src/main/cpp/SkyBox.cpp + src/main/cpp/SkinningBuffer.cpp src/main/cpp/Stream.cpp src/main/cpp/SurfaceOrientation.cpp src/main/cpp/SwapChain.cpp diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index d93719d701..fb77f0ed47 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -170,6 +170,14 @@ Java_com_google_android_filament_RenderableManager_nBuilderScreenSpaceContactSha builder->screenSpaceContactShadows(enabled); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nBuilderSkinningBuffer(JNIEnv*, jclass, + jlong nativeBuilder, jlong nativeSkinningBuffer, jint boneCount, jint offset) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + SkinningBuffer *skinningBuffer = (SkinningBuffer *) nativeSkinningBuffer; + builder->skinning(skinningBuffer, boneCount, offset); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nBuilderSkinning(JNIEnv*, jclass, jlong nativeBuilder, jint boneCount) { @@ -177,6 +185,13 @@ Java_com_google_android_filament_RenderableManager_nBuilderSkinning(JNIEnv*, jcl builder->skinning((size_t)boneCount); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nEnableSkinningBuffers(JNIEnv*, jclass, + jlong nativeBuilder, jboolean enabled) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + builder->enableSkinningBuffers(enabled); +} + extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_RenderableManager_nBuilderSkinningBones(JNIEnv* env, jclass, jlong nativeBuilder, jint boneCount, jobject bones, jint remaining) { @@ -199,6 +214,13 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphing(JNIEnv*, jcl builder->morphing(enabled); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nSetSkinningBuffer(JNIEnv*, jclass, + jlong nativeRenderableManager, jint i, jlong nativeSkinningBuffer, jint count, jint offset) { + RenderableManager *rm = (RenderableManager *) nativeRenderableManager; + SkinningBuffer *sb = (SkinningBuffer *) nativeSkinningBuffer; + rm->setSkinningBuffer(i, sb, count, offset); +} extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_RenderableManager_nSetBonesAsMatrices(JNIEnv* env, jclass, @@ -339,21 +361,13 @@ Java_com_google_android_filament_RenderableManager_nSetMaterialInstanceAt(JNIEnv materialInstance); } -extern "C" JNIEXPORT long JNICALL +extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_RenderableManager_nGetMaterialInstanceAt(JNIEnv*, jclass, jlong nativeRenderableManager, jint i, jint primitiveIndex) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; return (long) rm->getMaterialInstanceAt((RenderableManager::Instance) i, (size_t) primitiveIndex); } -extern "C" JNIEXPORT long JNICALL -Java_com_google_android_filament_RenderableManager_nGetMaterialAt(JNIEnv*, jclass, - jlong nativeRenderableManager, jint i, jint primitiveIndex) { - RenderableManager *rm = (RenderableManager *) nativeRenderableManager; - MaterialInstance *mi = rm->getMaterialInstanceAt((RenderableManager::Instance) i, (size_t) primitiveIndex); - return (long) mi->getMaterial(); -} - extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nSetGeometryAt__JIIIJJII(JNIEnv*, jclass, jlong nativeRenderableManager, jint i, jint primitiveIndex, jint primitiveType, diff --git a/android/filament-android/src/main/cpp/SkinningBuffer.cpp b/android/filament-android/src/main/cpp/SkinningBuffer.cpp new file mode 100644 index 0000000000..7980ffee79 --- /dev/null +++ b/android/filament-android/src/main/cpp/SkinningBuffer.cpp @@ -0,0 +1,116 @@ +/* + * 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_SkinningBuffer_nCreateBuilder(JNIEnv*, jclass) { + return (jlong) new SkinningBuffer::Builder(); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_SkinningBuffer_nDestroyBuilder(JNIEnv*, jclass, + jlong nativeBuilder) { + SkinningBuffer::Builder* builder = (SkinningBuffer::Builder *) nativeBuilder; + delete builder; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_SkinningBuffer_nBuilderBoneCount(JNIEnv*, jclass, + jlong nativeBuilder, jint boneCount) { + SkinningBuffer::Builder* builder = (SkinningBuffer::Builder *) nativeBuilder; + builder->boneCount((uint32_t)boneCount); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_SkinningBuffer_nBuilderInitialize(JNIEnv*, jclass, + jlong nativeBuilder, jboolean initialize) { + SkinningBuffer::Builder* builder = (SkinningBuffer::Builder *) nativeBuilder; + builder->initialize((bool)initialize); +} + +extern "C" +JNIEXPORT jlong JNICALL +Java_com_google_android_filament_SkinningBuffer_nBuilderBuild(JNIEnv*, jclass, + jlong nativeBuilder, jlong nativeEngine) { + SkinningBuffer::Builder* builder = (SkinningBuffer::Builder *) nativeBuilder; + Engine *engine = (Engine *) nativeEngine; + return (jlong) builder->build(*engine); +} + +// ------------------------------------------------------------------------------------------------ + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_SkinningBuffer_nSetBonesAsMatrices(JNIEnv* env, jclass, + jlong nativeSkinningBuffer, jlong nativeEngine, jobject matrices, jint remaining, jint boneCount, + jint offset) { + SkinningBuffer *skinningBuffer = (SkinningBuffer *) nativeSkinningBuffer; + Engine *engine = (Engine *) nativeEngine; + AutoBuffer nioBuffer(env, matrices, boneCount * 16); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } + skinningBuffer->setBones(*engine, + static_cast(data), (size_t)boneCount, (size_t)offset); + return 0; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_SkinningBuffer_nSetBonesAsQuaternions(JNIEnv* env, jclass, + jlong nativeSkinningBuffer, jlong nativeEngine, jobject quaternions, jint remaining, + jint boneCount, jint offset) { + SkinningBuffer *skinningBuffer = (SkinningBuffer *) nativeSkinningBuffer; + Engine *engine = (Engine *) nativeEngine; + AutoBuffer nioBuffer(env, quaternions, boneCount * 8); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } + skinningBuffer->setBones(*engine, + static_cast(data), (size_t)boneCount, (size_t)offset); + return 0; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_google_android_filament_SkinningBuffer_nGetBoneCount(JNIEnv*, jclass, + jlong nativeSkinningBuffer) { + SkinningBuffer *skinningBuffer = (SkinningBuffer *) nativeSkinningBuffer; + return (jint)skinningBuffer->getBoneCount(); +} 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 847d641c6a..49e1570719 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 @@ -113,7 +113,7 @@ public class RenderableManager { * @param count the number of primitives that will be supplied to the builder * * Note that builders typically do not have a long lifetime since clients should discard - * them after calling build(). For a usage example, see {@link RenderableManager}. + * them after calling {@link #build}. For a usage example, see {@link RenderableManager}. */ public Builder(@IntRange(from = 1) int count) { mNativeBuilder = nCreateBuilder(count); @@ -128,7 +128,7 @@ public class RenderableManager { * geometry() and material(). * * @param index zero-based index of the primitive, must be less than the count passed to Builder constructor - * @param type specifies the topology of the primitive (e.g., PrimitiveType.TRIANGLES) + * @param type specifies the topology of the primitive (e.g., {@link PrimitiveType#TRIANGLES}) * @param vertices specifies the vertex buffer, which in turn specifies a set of attributes * @param indices specifies the index buffer (either u16 or u32) * @param offset specifies where in the index buffer to start reading (expressed as a number of indices) @@ -147,7 +147,7 @@ public class RenderableManager { } /** - * For details, see the {@link RenderableManager.Builder#geometry primary overload}. + * For details, see the {@link RenderableManager.Builder#geometry} primary overload. */ @NonNull public Builder geometry(@IntRange(from = 0) int index, @NonNull PrimitiveType type, @@ -159,7 +159,7 @@ public class RenderableManager { } /** - * For details, see the {@link RenderableManager.Builder#geometry primary overload}. + * For details, see the {@link RenderableManager.Builder#geometry} primary overload. */ @NonNull public Builder geometry(@IntRange(from = 0) int index, @NonNull PrimitiveType type, @@ -303,6 +303,50 @@ public class RenderableManager { return this; } + /** + * Allows bones to be swapped out and shared using SkinningBuffer. + * + * If skinning buffer mode is enabled, clients must call #setSkinningBuffer() rather than + * #setBonesAsQuaternions(). This allows sharing of data between renderables. + * + * @param enabled If true, enables buffer object mode. False by default. + */ + @NonNull + public Builder enableSkinningBuffers(boolean enabled) { + nEnableSkinningBuffers(mNativeBuilder, enabled); + return this; + } + + /** + * Enables GPU vertex skinning for up to 255 bones, 0 by default. + * + *

Skinning Buffer mode must be enabled.

+ * + *

Each vertex can be affected by up to 4 bones simultaneously. The attached + * VertexBuffer must provide data in the BONE_INDICES slot (uvec4) and the + * BONE_WEIGHTS slot (float4).

+ * + *

See also {@link #setSkinningBuffer}, {@link SkinningBuffer#setBonesAsMatrices} + * or {@link SkinningBuffer#setBonesAsQuaternions}, + * which can be called on a per-frame basis to advance the animation.

+ * + * @see #setSkinningBuffer + * @see SkinningBuffer#setBonesAsMatrices + * @see SkinningBuffer#setBonesAsQuaternions + * + * @param skinningBuffer null to disable, otherwise the {@link SkinningBuffer} to use + * @param boneCount 0 to disable, otherwise the number of bone transforms (up to 255) + * @param offset offset in the {@link SkinningBuffer} + * @return this Builder object for chaining calls + */ + @NonNull + public Builder skinning(SkinningBuffer skinningBuffer, + @IntRange(from = 0, to = 255) int boneCount, int offset) { + nBuilderSkinningBuffer(mNativeBuilder, + skinningBuffer != null ? skinningBuffer.getNativeObject() : 0, boneCount, offset); + return this; + } + @NonNull public Builder skinning(@IntRange(from = 0, to = 255) int boneCount) { nBuilderSkinning(mNativeBuilder, boneCount); @@ -312,6 +356,8 @@ public class RenderableManager { /** * Enables GPU vertex skinning for up to 255 bones, 0 by default. * + *

Skinning Buffer mode must be disabled.

+ * *

Each vertex can be affected by up to 4 bones simultaneously. The attached * VertexBuffer must provide data in the BONE_INDICES slot (uvec4) and the * BONE_WEIGHTS slot (float4).

@@ -319,6 +365,8 @@ public class RenderableManager { *

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

* + * @see SkinningBuffer#setBonesAsMatrices + * * @param boneCount Number of bones associated with this component * @param bones A FloatBuffer containing boneCount transforms. Each transform consists of 8 float. * float 0 to 3 encode a unit quaternion w+ix+jy+kz stored as x,y,z,w. @@ -379,9 +427,22 @@ public class RenderableManager { } } + + /** + * Associates a {@link SkinningBuffer} to a renderable instance + * @param i Instance of the Renderable + * @param skinningBuffer {@link SkinningBuffer} to use + * @param count Numbers of bones to set + * @param offset Offset in the {@link SkinningBuffer} + */ + public void setSkinningBuffer(@EntityInstance int i, @NonNull SkinningBuffer skinningBuffer, + int count, int offset) { + nSetSkinningBuffer(mNativeObject, i, skinningBuffer.getNativeObject(), count, offset); + } + /** * Sets the transforms associated with each bone of a Renderable. - * + * * @param i Instance of the Renderable * @param matrices A FloatBuffer containing boneCount 4x4 packed matrices (i.e. 16 floats each matrix and no gap between matrices) * @param boneCount Number of bones to set @@ -654,8 +715,11 @@ public class RenderableManager { private static native void nBuilderScreenSpaceContactShadows(long nativeBuilder, boolean enabled); private static native void nBuilderSkinning(long nativeBuilder, int boneCount); private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining); + private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset); private static native void nBuilderMorphing(long nativeBuilder, boolean enabled); + private static native void nEnableSkinningBuffers(long nativeBuilder, boolean enabled); + private static native void nSetSkinningBuffer(long nativeObject, int i, long nativeSkinningBuffer, int count, int offset); 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); @@ -672,7 +736,6 @@ public class RenderableManager { private static native int nGetPrimitiveCount(long nativeRenderableManager, int i); private static native void nSetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex, long nativeMaterialInstance); private static native long nGetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex); - private static native long nGetMaterialAt(long nativeRenderableManager, int i, int primitiveIndex); private static native void nSetGeometryAt(long nativeRenderableManager, int i, int primitiveIndex, int primitiveType, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count); private static native void nSetGeometryAt(long nativeRenderableManager, int i, int primitiveIndex, int primitiveType, int offset, int count); private static native void nSetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex, int blendOrder); diff --git a/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java new file mode 100644 index 0000000000..c255e6b65a --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java @@ -0,0 +1,177 @@ +/* + * 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. + */ + +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 SkinningBuffer { + private long mNativeObject; + private SkinningBuffer(long nativeSkinningBuffer) { + mNativeObject = nativeSkinningBuffer; + } + + public static class Builder { + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + // Keep to finalize native resources + private final SkinningBuffer.Builder.BuilderFinalizer mFinalizer; + private final long mNativeBuilder; + + public Builder() { + mNativeBuilder = nCreateBuilder(); + mFinalizer = new SkinningBuffer.Builder.BuilderFinalizer(mNativeBuilder); + } + + /** + * Size of the skinning buffer in bones. + * + *

Due to limitation in the GLSL, the SkinningBuffer must always by a multiple of + * 256, this adjustment is done automatically, but can cause + * some memory overhead. This memory overhead can be mitigated by using the same + * {@link SkinningBuffer} to store the bone information for multiple RenderPrimitives.

+ * + * @param boneCount Number of bones the skinning buffer can hold. + * @return this Builder object for chaining calls + */ + @NonNull + public Builder boneCount(@IntRange(from = 1) int boneCount) { + nBuilderBoneCount(mNativeBuilder, boneCount); + return this; + } + + /** + * The new buffer is created with identity bones + * + * @param initialize true to initializing the buffer, false to not. + * @return A reference to this Builder for chaining calls. + */ + @NonNull + public Builder initialize(boolean initialize) { + nBuilderInitialize(mNativeBuilder, initialize); + return this; + } + + /** + * Creates and returns the SkinningBuffer object. + * + * @param engine reference to the {@link Engine} to associate this SkinningBuffer + * with. + * + * @return the newly created SkinningBuffer object + * + * @exception IllegalStateException if the SkinningBuffer could not be created + * + * @see #setBonesAsMatrices + * @see #setBonesAsQuaternions + */ + @NonNull + public SkinningBuffer build(@NonNull Engine engine) { + long nativeSkinningBuffer = nBuilderBuild(mNativeBuilder, engine.getNativeObject()); + if (nativeSkinningBuffer == 0) + throw new IllegalStateException("Couldn't create SkinningBuffer"); + return new SkinningBuffer(nativeSkinningBuffer); + } + + 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 the bone transforms in the range [offset, offset + boneCount). + * + * @param engine {@link Engine} instance + * @param matrices A {@link FloatBuffer} containing boneCount 4x4 packed matrices (i.e. 16 floats each matrix and no gap between matrices) + * @param boneCount Number of bones to set + * @param offset Index of the first bone to set + */ + public void setBonesAsMatrices(@NonNull Engine engine, + @NonNull Buffer matrices, @IntRange(from = 0, to = 255) int boneCount, + @IntRange(from = 0) int offset) { + int result = nSetBonesAsMatrices(mNativeObject, engine.getNativeObject(), + matrices, matrices.remaining(), boneCount, offset); + if (result < 0) { + throw new BufferOverflowException(); + } + } + + /** + * Updates the bone transforms in the range [offset, offset + boneCount). + * + * @param engine {@link Engine} instance + * @param quaternions A {@link FloatBuffer} containing boneCount transforms. Each transform consists of 8 float. + * float 0 to 3 encode a unit quaternion w+ix+jy+kz stored as x,y,z,w. + * float 4 to 7 encode a translation stored as x,y,z,1. + * @param boneCount Number of bones to set + * @param offset Index of the first bone to set + */ + public void setBonesAsQuaternions(@NonNull Engine engine, + @NonNull Buffer quaternions, @IntRange(from = 0, to = 255) int boneCount, + @IntRange(from = 0) int offset) { + int result = nSetBonesAsQuaternions(mNativeObject, engine.getNativeObject(), + quaternions, quaternions.remaining(), boneCount, offset); + if (result < 0) { + throw new BufferOverflowException(); + } + } + + /** + * @return number of bones in this {@link SkinningBuffer} + */ + public int getBoneCount() { + return nGetBoneCount(mNativeObject); + } + + public long getNativeObject() { + if (mNativeObject == 0) { + throw new IllegalStateException("Calling method on destroyed IndexBuffer"); + } + return mNativeObject; + } + + void clearNativeObject() { + mNativeObject = 0; + } + + private static native long nCreateBuilder(); + private static native void nDestroyBuilder(long nativeBuilder); + private static native void nBuilderBoneCount(long nativeBuilder, int boneCount); + private static native void nBuilderInitialize(long nativeBuilder, boolean initialize); + private static native long nBuilderBuild(long nativeBuilder, long nativeEngine); + + private static native int nSetBonesAsMatrices(long nativeObject, long nativeEngine, Buffer matrices, int remaining, int boneCount, int offset); + private static native int nSetBonesAsQuaternions(long nativeObject, long nativeEngine, Buffer quaternions, int remaining, int boneCount, int offset); + private static native int nGetBoneCount(long nativeObject); +} diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index f695175d2f..ab97946c93 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -32,6 +32,7 @@ set(PUBLIC_HDRS include/filament/RenderableManager.h include/filament/Renderer.h include/filament/Scene.h + include/filament/SkinningBuffer.h include/filament/Skybox.h include/filament/Stream.h include/filament/SwapChain.h @@ -73,6 +74,7 @@ set(SRCS src/Scene.cpp src/ShadowMap.cpp src/ShadowMapManager.cpp + src/SkinningBuffer.cpp src/Skybox.cpp src/Stream.cpp src/SwapChain.cpp @@ -135,6 +137,7 @@ set(PRIVATE_HDRS src/details/ShadowMap.h src/details/ShadowMapManager.h src/details/Skybox.h + src/details/SkinningBuffer.h src/details/Stream.h src/details/SwapChain.h src/details/Texture.h diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 0477633742..8f2b6775bb 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -36,11 +36,13 @@ namespace utils { namespace filament { +class BufferObject; class Engine; class IndexBuffer; class Material; class MaterialInstance; class Renderer; +class SkinningBuffer; class VertexBuffer; class FEngine; @@ -239,9 +241,40 @@ public: */ Builder& screenSpaceContactShadows(bool enable) noexcept; + /** + * Allows bones to be swapped out and shared using SkinningBuffer. + * + * If skinning buffer mode is enabled, clients must call setSkinningBuffer() rather than + * setBones(). This allows sharing of data between renderables. + * + * @param enabled If true, enables buffer object mode. False by default. + */ + Builder& enableSkinningBuffers(bool enabled = true) noexcept; + /** * Enables GPU vertex skinning for up to 255 bones, 0 by default. * + * Skinning Buffer mode must be enabled. + * + * Each vertex can be affected by up to 4 bones simultaneously. The attached + * VertexBuffer must provide data in the \c BONE_INDICES slot (uvec4) and the + * \c BONE_WEIGHTS slot (float4). + * + * See also RenderableManager::setSkinningBuffer() or SkinningBuffer::setBones(), + * which can be called on a per-frame basis to advance the animation. + * + * @param skinningBuffer nullptr to disable, otherwise the SkinningBuffer to use + * @param count 0 to disable, otherwise the number of bone transforms (up to 255) + * @param offset offset in the SkinningBuffer + */ + Builder& skinning(SkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept; + + + /** + * Enables GPU vertex skinning for up to 255 bones, 0 by default. + * + * Skinning Buffer mode must be disabled. + * * Each vertex can be affected by up to 4 bones simultaneously. The attached * VertexBuffer must provide data in the \c BONE_INDICES slot (uvec4) and the * \c BONE_WEIGHTS slot (float4). @@ -390,6 +423,12 @@ public: void setBones(Instance instance, Bone const* transforms, size_t boneCount = 1, size_t offset = 0) noexcept; void setBones(Instance instance, math::mat4f const* transforms, size_t boneCount = 1, size_t offset = 0) noexcept; //!< \overload + /** + * Associates a SkinningBuffer to a renderable instance + */ + void setSkinningBuffer(Instance instance, SkinningBuffer* skinningBuffer, + size_t count, size_t offset) noexcept; + /** * Updates the vertex morphing weights on a renderable, all zeroes by default. * diff --git a/filament/include/filament/SkinningBuffer.h b/filament/include/filament/SkinningBuffer.h new file mode 100644 index 0000000000..ca313d74cb --- /dev/null +++ b/filament/include/filament/SkinningBuffer.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_SKINNINGBUFFER_H +#define TNT_FILAMENT_SKINNINGBUFFER_H + +#include + +#include + +#include + +#include + +#include + + +namespace filament { + +/** + * SkinningBuffer is used to hold skinning data (bones). It is a simple wraper around + * a structured UBO. + */ +class UTILS_PUBLIC SkinningBuffer : 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 skinning buffer in bones. + * + * Due to limitation in the GLSL, the SkinningBuffer must always by a multiple of + * 256, this adjustment is done automatically, but can cause + * some memory overhead. This memory overhead can be mitigated by using the same + * SkinningBuffer to store the bone information for multiple RenderPrimitives. + * + * @param boneCount Number of bones the skinning buffer can hold. + * @return A reference to this Builder for chaining calls. + */ + Builder& boneCount(uint32_t boneCount) noexcept; + + /** + * The new buffer is created with identity bones + * @param initialize true to initializing the buffer, false to not. + * @return A reference to this Builder for chaining calls. + */ + Builder& initialize(bool initialize = true) noexcept; + + /** + * Creates the SkinningBuffer object and returns a pointer to it. + * + * @param engine Reference to the filament::Engine to associate this SkinningBuffer 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. + * + * @see SkinningBuffer::setBones + */ + SkinningBuffer* build(Engine& engine); + private: + friend class FSkinningBuffer; + }; + + /** + * Updates the bone transforms in the range [offset, offset + count). + * @param engine Reference to the filament::Engine to associate this SkinningBuffer with. + * @param transforms pointer to at least count Bone + * @param count number of Bone elements in transforms + * @param offset offset in elements (not bytes) in the SkinningBuffer (not in transforms) + */ + void setBones(Engine& engine, RenderableManager::Bone const* transforms, + size_t count, size_t offset = 0); + + /** + * Updates the bone transforms in the range [offset, offset + count). + * @param engine Reference to the filament::Engine to associate this SkinningBuffer with. + * @param transforms pointer to at least count mat4f + * @param count number of mat4f elements in transforms + * @param offset offset in elements (not bytes) in the SkinningBuffer (not in transforms) + */ + void setBones(Engine& engine, math::mat4f const* transforms, + size_t count, size_t offset = 0); + + /** + * Returns the size of this SkinningBuffer in elements. + * @return The number of bones the SkinningBuffer holds. + */ + size_t getBoneCount() const noexcept; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_SKINNINGBUFFER_H diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 03e8a4dc7e..3b968db294 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -32,6 +32,7 @@ #include "details/RenderPrimitive.h" #include "details/Scene.h" #include "details/Skybox.h" +#include "details/SkinningBuffer.h" #include "details/Stream.h" #include "details/SwapChain.h" #include "details/Texture.h" @@ -330,6 +331,7 @@ void FEngine::shutdown() { cleanupResourceList(mBufferObjects); cleanupResourceList(mIndexBuffers); + cleanupResourceList(mSkinningBuffers); cleanupResourceList(mVertexBuffers); cleanupResourceList(mTextures); cleanupResourceList(mRenderTargets); @@ -565,6 +567,10 @@ FIndexBuffer* FEngine::createIndexBuffer(const IndexBuffer::Builder& builder) no return create(mIndexBuffers, builder); } +FSkinningBuffer* FEngine::createSkinningBuffer(const SkinningBuffer::Builder& builder) noexcept { + return create(mSkinningBuffers, builder); +} + FTexture* FEngine::createTexture(const Texture::Builder& builder) noexcept { return create(mTextures, builder); } @@ -747,6 +753,10 @@ bool FEngine::destroy(const FIndexBuffer* p) { return terminateAndDestroy(p, mIndexBuffers); } +bool FEngine::destroy(const FSkinningBuffer* p) { + return terminateAndDestroy(p, mSkinningBuffers); +} + inline bool FEngine::destroy(const FRenderer* p) { return terminateAndDestroy(p, mRenderers); } diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 8651449a7b..b1d1e2dd77 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -278,7 +278,6 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, auto const* const UTILS_RESTRICT soaReversedWinding = soa.data(); auto const* const UTILS_RESTRICT soaVisibility = soa.data(); auto const* const UTILS_RESTRICT soaPrimitives = soa.data(); - auto const* const UTILS_RESTRICT soaBonesUbh = soa.data(); auto const* const UTILS_RESTRICT soaVisibilityMask = soa.data(); const bool hasShadowing = renderFlags & HAS_SHADOWING; @@ -352,7 +351,6 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdColor.key = makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT); cmdColor.primitive.index = (uint16_t)i; - cmdColor.primitive.perRenderableBones = soaBonesUbh[i]; materialVariant.setShadowReceiver(soaVisibility[i].receiveShadows & hasShadowing); materialVariant.setSkinning(soaVisibility[i].skinning || soaVisibility[i].morphing); @@ -363,7 +361,6 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdDepth.key |= makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT); cmdDepth.key |= makeField(distanceBits, DISTANCE_BITS_MASK, DISTANCE_BITS_SHIFT); cmdDepth.primitive.index = (uint16_t)i; - cmdDepth.primitive.perRenderableBones = soaBonesUbh[i]; cmdDepth.primitive.materialVariant.setSkinning(soaVisibility[i].skinning || soaVisibility[i].morphing); cmdDepth.primitive.rasterState.inverseFrontFaces = inverseFrontFaces; @@ -514,18 +511,21 @@ void RenderPass::Executor::execute(const char* name, engine.flush(); driver.beginRenderPass(renderTarget, params); - recordDriverCommands(driver, mBegin, mEnd); + recordDriverCommands(driver, mBegin, mEnd, mRenderableSoa); driver.endRenderPass(); } UTILS_NOINLINE // no need to be inlined void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver, - const Command* first, const Command* last) const noexcept { + const Command* first, const Command* last, + FScene::RenderableSoa const& soa) const noexcept { SYSTRACE_CALL(); if (first != last) { SYSTRACE_VALUE32("commandCount", last - first); + auto const* const UTILS_RESTRICT soaSkinning = soa.data(); + PolygonOffset dummyPolyOffset; PipelineState pipeline{ .polygonOffset = mPolygonOffset }; PolygonOffset* const pPipelinePolygonOffset = @@ -564,14 +564,18 @@ void RenderPass::Executor::recordDriverCommands(backend::DriverApi& driver, size_t offset = info.index * sizeof(PerRenderableUib); driver.bindUniformBufferRange(BindingPoints::PER_RENDERABLE, uboHandle, offset, sizeof(PerRenderableUib)); - if (UTILS_UNLIKELY(info.perRenderableBones)) { - driver.bindUniformBuffer(BindingPoints::PER_RENDERABLE_BONES, - info.perRenderableBones); + + auto skinning = soaSkinning[info.index]; + if (UTILS_UNLIKELY(skinning.handle)) { + // note: we can't bind less than CONFIG_MAX_BONE_COUNT due to glsl limitations + driver.bindUniformBufferRange(BindingPoints::PER_RENDERABLE_BONES, + skinning.handle, + skinning.offset * sizeof(PerRenderableUibBone), + CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone)); } driver.draw(pipeline, info.primitiveHandle); } } } - } // namespace filament diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 6ae5727d6a..75c077b2d2 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -210,13 +210,12 @@ public: struct PrimitiveInfo { // 24 bytes FMaterialInstance const* mi = nullptr; // 8 bytes (4) backend::Handle primitiveHandle; // 4 bytes - backend::Handle perRenderableBones; // 4 bytes backend::RasterState rasterState; // 4 bytes uint16_t index = 0; // 2 bytes Variant materialVariant; // 1 byte - uint8_t reserved = {}; // 1 byte + uint8_t reserved[13 - sizeof(void*)] = {}; // 5 byte (9) }; - static_assert(sizeof(PrimitiveInfo) == sizeof(void*) + 16); + static_assert(sizeof(PrimitiveInfo) == 24); struct alignas(8) Command { // 32 bytes CommandKey key = 0; // 8 bytes @@ -312,13 +311,14 @@ public: FEngine& mEngine; Command const* mBegin; Command const* mEnd; + FScene::RenderableSoa const& mRenderableSoa; const CustomCommandVector mCustomCommands; const backend::Handle mUboHandle; const backend::PolygonOffset mPolygonOffset; const bool mPolygonOffsetOverride; Executor(RenderPass const* pass, Command const* b, Command const* e) noexcept - : mEngine(pass->mEngine), mBegin(b), mEnd(e), + : mEngine(pass->mEngine), mBegin(b), mEnd(e), mRenderableSoa(*pass->mRenderableSoa), mCustomCommands(pass->mCustomCommands), mUboHandle(pass->mUboHandle), mPolygonOffset(pass->mPolygonOffset), mPolygonOffsetOverride(pass->mPolygonOffsetOverride) { @@ -326,8 +326,9 @@ public: assert_invariant(e <= pass->end()); } - void recordDriverCommands(backend::DriverApi& driver, const Command* first, - const Command* last) const noexcept; + void recordDriverCommands(backend::DriverApi& driver, + const Command* first, const Command* last, + FScene::RenderableSoa const& soa) const noexcept; public: void execute(const char* name, diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 0aa20d1ef0..fdc225c304 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -132,19 +132,19 @@ void FScene::prepare(const mat4f& worldOriginTransform, bool shadowReceiversAreC // we know there is enough space in the array sceneData.push_back_unsafe( - ri, // RENDERABLE_INSTANCE - worldTransform, // WORLD_TRANSFORM - reversedWindingOrder, // REVERSED_WINDING_ORDER - visibility, // VISIBILITY_STATE - rcm.getBonesUbh(ri), // BONES_UBH - worldAABB.center, // WORLD_AABB_CENTER - 0, // VISIBLE_MASK - rcm.getMorphWeights(ri), // MORPH_WEIGHTS - rcm.getLayerMask(ri), // LAYERS - worldAABB.halfExtent, // WORLD_AABB_EXTENT - {}, // PRIMITIVES - 0, // SUMMED_PRIMITIVE_COUNT - scale // USER_DATA + ri, // RENDERABLE_INSTANCE + worldTransform, // WORLD_TRANSFORM + reversedWindingOrder, // REVERSED_WINDING_ORDER + visibility, // VISIBILITY_STATE + rcm.getSkinningBufferInfo(ri), // SKINNING_BUFFER + worldAABB.center, // WORLD_AABB_CENTER + 0, // VISIBLE_MASK + rcm.getMorphWeights(ri), // MORPH_WEIGHTS + rcm.getLayerMask(ri), // LAYERS + worldAABB.halfExtent, // WORLD_AABB_EXTENT + {}, // PRIMITIVES + 0, // SUMMED_PRIMITIVE_COUNT + scale // USER_DATA ); } diff --git a/filament/src/SkinningBuffer.cpp b/filament/src/SkinningBuffer.cpp new file mode 100644 index 0000000000..d7c58e7875 --- /dev/null +++ b/filament/src/SkinningBuffer.cpp @@ -0,0 +1,180 @@ +/* + * 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/SkinningBuffer.h" + +#include "components/RenderableManager.h" + +#include "details/Engine.h" + +#include "FilamentAPI-impl.h" + +#include + +namespace filament { + +using namespace backend; +using namespace math; + +struct SkinningBuffer::BuilderDetails { + uint32_t mBoneCount = 0; + bool mInitialize = false; +}; + +using BuilderType = SkinningBuffer; +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; + + +SkinningBuffer::Builder& SkinningBuffer::Builder::boneCount(uint32_t boneCount) noexcept { + mImpl->mBoneCount = boneCount; + return *this; +} + +SkinningBuffer::Builder& SkinningBuffer::Builder::initialize(bool initialize) noexcept { + mImpl->mInitialize = initialize; + return *this; +} + +SkinningBuffer* SkinningBuffer::Builder::build(Engine& engine) { + return upcast(engine).createSkinningBuffer(*this); +} + +// ------------------------------------------------------------------------------------------------ + +FSkinningBuffer::FSkinningBuffer(FEngine& engine, const Builder& builder) + : mBoneCount(builder->mBoneCount) { + FEngine::DriverApi& driver = engine.getDriverApi(); + + // According to the OpenGL ES 3.2 specification in 7.6.3 Uniform + // Buffer Object Bindings: + // + // the uniform block must be populated with a buffer object with a size no smaller + // than the minimum required size of the uniform block (the value of + // UNIFORM_BLOCK_DATA_SIZE). + + mHandle = driver.createBufferObject( + getPhysicalBoneCount(mBoneCount) * sizeof(PerRenderableUibBone), + BufferObjectBinding::UNIFORM, + BufferUsage::DYNAMIC); + + if (builder->mInitialize) { + // initialize the bones to identity (before rounding up) + size_t size = mBoneCount * sizeof(PerRenderableUibBone); + auto* out = (PerRenderableUibBone*)driver.allocate(size); + std::uninitialized_fill_n(out, mBoneCount, PerRenderableUibBone{}); + driver.updateBufferObject(mHandle, { out, size }, 0); + } +} + +void FSkinningBuffer::terminate(FEngine& engine) { + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.destroyBufferObject(mHandle); +} + +void FSkinningBuffer::setBones(FEngine& engine, + RenderableManager::Bone const* transforms, size_t count, size_t offset) { + + ASSERT_PRECONDITION((offset + count) <= mBoneCount, + "SkinningBuffer (size=%lu) overflow (boneCount=%u, offset=%u)", + (unsigned)mBoneCount, (unsigned)count, (unsigned)offset); + + setBones(engine, mHandle, transforms, count, offset); +} + +void FSkinningBuffer::setBones(FEngine& engine, + math::mat4f const* transforms, size_t count, size_t offset) { + + ASSERT_PRECONDITION((offset + count) <= mBoneCount, + "SkinningBuffer (size=%lu) overflow (boneCount=%u, offset=%u)", + (unsigned)mBoneCount, (unsigned)count, (unsigned)offset); + + setBones(engine, mHandle, transforms, count, offset); +} + +void FSkinningBuffer::setBones(FEngine& engine, Handle handle, + RenderableManager::Bone const* transforms, size_t boneCount, size_t offset) noexcept { + auto& driverApi = engine.getDriverApi(); + size_t size = boneCount * sizeof(PerRenderableUibBone); + PerRenderableUibBone* UTILS_RESTRICT out = (PerRenderableUibBone*)driverApi.allocate(size); + for (size_t i = 0, c = boneCount; i < c; ++i) { + out[i].q = transforms[i].unitQuaternion; + out[i].t.xyz = transforms[i].translation; + out[i].s = out[i].ns = { 1, 1, 1, 0 }; + } + driverApi.updateBufferObject(handle, { out, size }, + offset * sizeof(PerRenderableUibBone)); +} + +void FSkinningBuffer::setBones(FEngine& engine, Handle handle, + mat4f const* transforms, size_t boneCount, size_t offset) noexcept { + auto& driverApi = engine.getDriverApi(); + size_t size = boneCount * sizeof(PerRenderableUibBone); + PerRenderableUibBone* UTILS_RESTRICT out = (PerRenderableUibBone*)driverApi.allocate(size); + for (size_t i = 0, c = boneCount; i < c; ++i) { + FSkinningBuffer::makeBone(&out[i], transforms[i]); + } + driverApi.updateBufferObject(handle, { out, size }, + offset * sizeof(PerRenderableUibBone)); +} + +void FSkinningBuffer::makeBone(PerRenderableUibBone* UTILS_RESTRICT out, mat4f const& t) noexcept { + mat4f m(t); + + // figure out the scales + float4 s = { length(m[0]), length(m[1]), length(m[2]), 0.0f }; + if (dot(cross(m[0].xyz, m[1].xyz), m[2].xyz) < 0) { + s[2] = -s[2]; + } + + // compute the inverse scales + float4 is = { 1.0f/s.x, 1.0f/s.y, 1.0f/s.z, 0.0f }; + + // normalize the matrix + m[0] *= is[0]; + m[1] *= is[1]; + m[2] *= is[2]; + + out->s = s; + out->q = m.toQuaternion(); + out->t = m[3]; + out->ns = is / max(abs(is)); +} + +// ------------------------------------------------------------------------------------------------ +// Trampoline calling into private implementation +// ------------------------------------------------------------------------------------------------ + +void SkinningBuffer::setBones(Engine& engine, + RenderableManager::Bone const* transforms, size_t count, size_t offset) { + upcast(this)->setBones(upcast(engine), transforms, count, offset); +} + +void SkinningBuffer::setBones(Engine& engine, + math::mat4f const* transforms, size_t count, size_t offset) { + upcast(this)->setBones(upcast(engine), transforms, count, offset); +} + +size_t SkinningBuffer::getBoneCount() const noexcept { + return upcast(this)->getBoneCount(); +} + +} // namespace filament + diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 144c19b20e..96fcaa0d49 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -574,10 +574,6 @@ void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& are s.fogInscatteringSize = fogOptions.inScatteringSize; s.fogColorFromIbl = fogOptions.fogColorFromIbl ? 1.0f : 0.0f; - // upload the renderables's dirty UBOs - engine.getRenderableManager().prepare(driver, - renderableData.data(), merged); - // set uniforms and samplers bindPerViewUniformsAndSamplers(driver); } diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 32f0543eca..b6353a8157 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -29,6 +29,8 @@ #include #include #include +#include + using namespace filament::math; using namespace utils; @@ -48,13 +50,17 @@ struct RenderableManager::BuilderDetails { bool mReceiveShadows : 1; bool mScreenSpaceContactShadows : 1; bool mMorphingEnabled : 1; + bool mSkinningBufferMode : 1; size_t mSkinningBoneCount = 0; Bone const* mUserBones = nullptr; mat4f const* mUserBoneMatrices = nullptr; + FSkinningBuffer* mSkinningBuffer = nullptr; + uint32_t mSkinningBufferOffset = 0; explicit BuilderDetails(size_t count) : mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true), - mScreenSpaceContactShadows(false), mMorphingEnabled(false) { + mScreenSpaceContactShadows(false), mMorphingEnabled(false), + mSkinningBufferMode(false) { } // this is only needed for the explicit instantiation below BuilderDetails() = default; @@ -161,6 +167,19 @@ RenderableManager::Builder& RenderableManager::Builder::skinning( return *this; } +RenderableManager::Builder& RenderableManager::Builder::skinning( + SkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept { + mImpl->mSkinningBuffer = upcast(skinningBuffer); + mImpl->mSkinningBoneCount = count; + mImpl->mSkinningBufferOffset = offset; + return *this; +} + +RenderableManager::Builder& RenderableManager::Builder::enableSkinningBuffers(bool enabled) noexcept { + mImpl->mSkinningBufferMode = enabled; + return *this; +} + RenderableManager::Builder& RenderableManager::Builder::morphing(bool enable) noexcept { mImpl->mMorphingEnabled = enable; return *this; @@ -289,39 +308,56 @@ void FRenderableManager::create( setMorphing(ci, builder->mMorphingEnabled); setMorphWeights(ci, {0, 0, 0, 0}); - const size_t count = builder->mSkinningBoneCount; - if (UTILS_UNLIKELY(count > 0 || builder->mMorphingEnabled)) { - std::unique_ptr& bones = manager[ci].bones; - // Note that we are sizing the bones UBO according to CONFIG_MAX_BONE_COUNT rather than - // mSkinningBoneCount. According to the OpenGL ES 3.2 specification in 7.6.3 Uniform - // Buffer Object Bindings: - // - // the uniform block must be populated with a buffer object with a size no smaller - // than the minimum required size of the uniform block (the value of - // UNIFORM_BLOCK_DATA_SIZE). - // - // This unfortunately means that we are using a large memory footprint for skinned - // renderables. In the future we could try addressing this by implementing a paging - // system such that multiple skinned renderables will share regions within a single - // large block of bones. - bones = std::unique_ptr(new Bones{ - driver.createBufferObject( - CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone), - BufferObjectBinding::UNIFORM, backend::BufferUsage::DYNAMIC), - UniformBuffer{ count * sizeof(PerRenderableUibBone) }, - count - }); - assert_invariant(bones); - if (bones) { + const uint32_t count = builder->mSkinningBoneCount; + if (builder->mSkinningBufferMode) { + if (builder->mSkinningBuffer) { setSkinning(ci, count > 0); - if (builder->mUserBones) { - setBones(ci, builder->mUserBones, count); - } else if (builder->mUserBoneMatrices) { - setBones(ci, builder->mUserBoneMatrices, count); - } else { - // initialize the bones to identity - PerRenderableUibBone* out = (PerRenderableUibBone*)bones->bones.invalidate(); - std::uninitialized_fill_n(out, count, PerRenderableUibBone{}); + Bones& bones = manager[ci].bones; + bones = Bones{ + .handle = builder->mSkinningBuffer->getHwHandle(), + .count = (uint16_t)count, + .offset = (uint16_t)builder->mSkinningBufferOffset, + .skinningBufferMode = true }; + } + } else { + if (UTILS_UNLIKELY(count > 0 || builder->mMorphingEnabled)) { + setSkinning(ci, count > 0); + Bones& bones = manager[ci].bones; + // Note that we are sizing the bones UBO according to CONFIG_MAX_BONE_COUNT rather than + // mSkinningBoneCount. According to the OpenGL ES 3.2 specification in 7.6.3 Uniform + // Buffer Object Bindings: + // + // the uniform block must be populated with a buffer object with a size no smaller + // than the minimum required size of the uniform block (the value of + // UNIFORM_BLOCK_DATA_SIZE). + // + // This unfortunately means that we are using a large memory footprint for skinned + // renderables. In the future we could try addressing this by implementing a paging + // system such that multiple skinned renderables will share regions within a single + // large block of bones. + bones = Bones{ + .handle = driver.createBufferObject( + CONFIG_MAX_BONE_COUNT * sizeof(PerRenderableUibBone), + BufferObjectBinding::UNIFORM, + backend::BufferUsage::DYNAMIC), + .count = (uint16_t)count, + .offset = 0, + .skinningBufferMode = false }; + + if (count) { + if (builder->mUserBones) { + FSkinningBuffer::setBones(mEngine, bones.handle, + builder->mUserBones, count, 0); + } else if (builder->mUserBoneMatrices) { + FSkinningBuffer::setBones(mEngine, bones.handle, + builder->mUserBoneMatrices, count, 0); + } else { + // initialize the bones to identity + size_t size = count * sizeof(PerRenderableUibBone); + auto* out = (PerRenderableUibBone*)driver.allocate(size); + std::uninitialized_fill_n(out, count, PerRenderableUibBone{}); + driver.updateBufferObject(bones.handle, { out, size }, 0); + } } } } @@ -365,9 +401,9 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept { destroyComponentPrimitives(engine, manager[ci].primitives); // destroy the bones structures if any - std::unique_ptr const& bones = manager[ci].bones; - if (bones) { - driver.destroyBufferObject(bones->handle); + Bones const& bones = manager[ci].bones; + if (bones.handle && !bones.skinningBufferMode) { + driver.destroyBufferObject(bones.handle); } } @@ -379,26 +415,6 @@ void FRenderableManager::destroyComponentPrimitives( delete[] primitives.data(); } - -void FRenderableManager::prepare( - backend::DriverApi& UTILS_RESTRICT driver, - Instance const* UTILS_RESTRICT instances, - utils::Range list) const noexcept { - const auto& manager = mManager; - - std::unique_ptr const * const UTILS_RESTRICT bones = manager.raw_array(); - for (uint32_t index : list) { - size_t i = instances[index].asValue(); - assert_invariant(i); // we should never get the null instance here - if (UTILS_UNLIKELY(bones[i])) { - if (bones[i]->bones.isDirty()) { - driver.updateBufferObject(bones[i]->handle, - bones[i]->bones.toBufferDescriptor(driver), 0); - } - } - } -} - void FRenderableManager::setMaterialInstanceAt(Instance instance, uint8_t level, size_t primitiveIndex, FMaterialInstance const* mi) noexcept { if (instance) { @@ -475,18 +491,15 @@ void FRenderableManager::setGeometryAt(Instance instance, uint8_t level, size_t void FRenderableManager::setBones(Instance ci, Bone const* UTILS_RESTRICT transforms, size_t boneCount, size_t offset) noexcept { if (ci) { - std::unique_ptr const& bones = mManager[ci].bones; - assert_invariant(bones && offset + boneCount <= bones->count); - if (bones) { - boneCount = std::min(boneCount, bones->count - offset); - PerRenderableUibBone* UTILS_RESTRICT out = (PerRenderableUibBone*)bones->bones.invalidateUniforms( - offset * sizeof(PerRenderableUibBone), - boneCount * sizeof(PerRenderableUibBone)); - for (size_t i = 0, c = boneCount; i < c; ++i) { - out[i].q = transforms[i].unitQuaternion; - out[i].t.xyz = transforms[i].translation; - out[i].s = out[i].ns = { 1, 1, 1, 0 }; - } + Bones& bones = mManager[ci].bones; + + ASSERT_PRECONDITION(!bones.skinningBufferMode, + "Disable skinning buffer mode to use this API"); + + assert_invariant(bones.handle && offset + boneCount <= bones.count); + if (bones.handle) { + boneCount = std::min(boneCount, bones.count - offset); + FSkinningBuffer::setBones(mEngine, bones.handle, transforms, boneCount, offset); } } } @@ -494,49 +507,56 @@ void FRenderableManager::setBones(Instance ci, void FRenderableManager::setBones(Instance ci, mat4f const* UTILS_RESTRICT transforms, size_t boneCount, size_t offset) noexcept { if (ci) { - std::unique_ptr const& bones = mManager[ci].bones; - assert_invariant(bones && offset + boneCount <= bones->count); - if (bones) { - boneCount = std::min(boneCount, bones->count - offset); - PerRenderableUibBone* UTILS_RESTRICT out = (PerRenderableUibBone*)bones->bones.invalidateUniforms( - offset * sizeof(PerRenderableUibBone), - boneCount * sizeof(PerRenderableUibBone)); - for (size_t i = 0, c = boneCount; i < c; ++i) { - makeBone(&out[i], transforms[i]); - } + Bones& bones = mManager[ci].bones; + + ASSERT_PRECONDITION(!bones.skinningBufferMode, + "Disable skinning buffer mode to use this API"); + + assert_invariant(bones.handle && offset + boneCount <= bones.count); + if (bones.handle) { + boneCount = std::min(boneCount, bones.count - offset); + FSkinningBuffer::setBones(mEngine, bones.handle, transforms, boneCount, offset); } } } +void FRenderableManager::setSkinningBuffer(FRenderableManager::Instance ci, + FSkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept { + + Bones& bones = mManager[ci].bones; + + ASSERT_PRECONDITION(bones.skinningBufferMode, + "Enable skinning buffer mode to use this API"); + + ASSERT_PRECONDITION( + count + offset < skinningBuffer->getBoneCount(), + "SkinningBuffer overflow (size=%u, count=%u, offset=%u)", + skinningBuffer->getBoneCount(), count, offset); + + // According to the OpenGL ES 3.2 specification in 7.6.3 Uniform + // Buffer Object Bindings: + // + // the uniform block must be populated with a buffer object with a size no smaller + // than the minimum required size of the uniform block (the value of + // UNIFORM_BLOCK_DATA_SIZE). + // + // So we round-up the "window" of bones set to match UNIFORM_BLOCK_DATA_SIZE, the SkinningBuffer + // should always contain enough date for this to work. + + count = FSkinningBuffer::getPhysicalBoneCount(count); + assert_invariant(count + offset < skinningBuffer->getBoneCount()); + + bones.handle = skinningBuffer->getHwHandle(); + bones.count = uint16_t(count); + bones.offset = uint16_t(offset); +} + void FRenderableManager::setMorphWeights(Instance ci, const float4& weights) noexcept { if (ci) { mManager[ci].morphWeights = weights; } } -void FRenderableManager::makeBone(PerRenderableUibBone* UTILS_RESTRICT out, mat4f const& t) noexcept { - mat4f m(t); - - // figure out the scales - float4 s = { length(m[0]), length(m[1]), length(m[2]), 0.0f }; - if (dot(cross(m[0].xyz, m[1].xyz), m[2].xyz) < 0) { - s[2] = -s[2]; - } - - // compute the inverse scales - float4 is = { 1.0f/s.x, 1.0f/s.y, 1.0f/s.z, 0.0f }; - - // normalize the matrix - m[0] *= is[0]; - m[1] *= is[1]; - m[2] *= is[2]; - - out->s = s; - out->q = m.toQuaternion(); - out->t = m[3]; - out->ns = is / max(abs(is)); -} - // ------------------------------------------------------------------------------------------------ // Trampoline calling into private implementation // ------------------------------------------------------------------------------------------------ @@ -646,4 +666,9 @@ void RenderableManager::setMorphWeights(Instance instance, float4 const& weights upcast(this)->setMorphWeights(instance, weights); } +void RenderableManager::setSkinningBuffer(RenderableManager::Instance instance, + SkinningBuffer* skinningBuffer, size_t count, size_t offset) noexcept { + upcast(this)->setSkinningBuffer(instance, upcast(skinningBuffer), count, offset); +} + } // namespace filament diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 481efe73cd..97ce2789c4 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -35,14 +35,13 @@ #include #include -// for gtest -class FilamentTest_Bones_Test; - namespace filament { +class FBufferObject; +class FIndexBuffer; class FMaterialInstance; class FRenderPrimitive; -class FIndexBuffer; +class FSkinningBuffer; class FVertexBuffer; class FRenderableManager : public RenderableManager { @@ -84,12 +83,6 @@ public: void destroy(utils::Entity e) noexcept; - // - instances is a list of Instance (typically the list from a given scene) - // - list is a list of index in 'instances' (typically the visible ones) - void prepare(backend::DriverApi& driver, - RenderableManager::Instance const* instances, - utils::Range list) const noexcept; - void gc(utils::EntityManager& em) noexcept { mManager.gc(em); } @@ -113,7 +106,8 @@ public: 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; inline bool isShadowCaster(Instance instance) const noexcept; inline bool isShadowReceiver(Instance instance) const noexcept; @@ -127,7 +121,12 @@ public: inline uint8_t getPriority(Instance instance) const noexcept; inline math::float4 getMorphWeights(Instance instance) const noexcept; - inline backend::Handle getBonesUbh(Instance instance) const noexcept; + struct SkinningBindingInfo { + backend::Handle handle; + uint32_t offset; + }; + + inline SkinningBindingInfo getSkinningBufferInfo(Instance instance) const noexcept; inline uint32_t getBoneCount(Instance instance) const noexcept; @@ -153,13 +152,12 @@ private: struct Bones { backend::Handle handle; - UniformBuffer bones; - size_t count; + uint16_t count = 0; + uint16_t offset = 0; + bool skinningBufferMode = false; }; - friend class ::FilamentTest_Bones_Test; - - static void makeBone(PerRenderableUibBone* out, math::mat4f const& transforms) noexcept; + static_assert(sizeof(Bones) == 12); enum { AABB, // user data @@ -176,7 +174,7 @@ private: math::float4, // MORPH_WEIGHTS Visibility, // VISIBILITY utils::Slice, // PRIMITIVES - std::unique_ptr // BONES + Bones // BONES >; struct Sim : public Base { @@ -323,14 +321,15 @@ Box const& FRenderableManager::getAABB(Instance instance) const noexcept { return mManager[instance].aabb; } -backend::Handle FRenderableManager::getBonesUbh(Instance instance) const noexcept { - std::unique_ptr const& bones = mManager[instance].bones; - return bones ? bones->handle : backend::Handle{}; +FRenderableManager::SkinningBindingInfo +FRenderableManager::getSkinningBufferInfo(Instance instance) const noexcept { + Bones const& bones = mManager[instance].bones; + return { bones.handle, bones.offset }; } inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexcept { - std::unique_ptr const& bones = mManager[instance].bones; - return bones ? bones->count : 0; + Bones const& bones = mManager[instance].bones; + return bones.count; } utils::Slice const& FRenderableManager::getRenderPrimitives( diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 9c75081b25..93d79398b5 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -35,6 +35,7 @@ #include "details/ResourceList.h" #include "details/ColorGrading.h" #include "details/Skybox.h" +#include "details/SkinningBuffer.h" #include "private/backend/CommandStream.h" #include "private/backend/CommandBufferQueue.h" @@ -232,6 +233,7 @@ public: FBufferObject* createBufferObject(const BufferObject::Builder& builder) noexcept; FVertexBuffer* createVertexBuffer(const VertexBuffer::Builder& builder) noexcept; FIndexBuffer* createIndexBuffer(const IndexBuffer::Builder& builder) noexcept; + FSkinningBuffer* createSkinningBuffer(const SkinningBuffer::Builder& builder) noexcept; FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept; FMaterial* createMaterial(const Material::Builder& builder) noexcept; FTexture* createTexture(const Texture::Builder& builder) noexcept; @@ -262,6 +264,7 @@ public: bool destroy(const FVertexBuffer* p); bool destroy(const FFence* p); bool destroy(const FIndexBuffer* p); + bool destroy(const FSkinningBuffer* p); bool destroy(const FIndirectLight* p); bool destroy(const FMaterial* p); bool destroy(const FMaterialInstance* p); @@ -368,6 +371,7 @@ private: ResourceList mSwapChains{ "SwapChain" }; ResourceList mStreams{ "Stream" }; ResourceList mIndexBuffers{ "IndexBuffer" }; + ResourceList mSkinningBuffers{ "SkinningBuffer" }; ResourceList mVertexBuffers{ "VertexBuffer" }; ResourceList mIndirectLights{ "IndirectLight" }; ResourceList mMaterials{ "Material" }; diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 2f96987fc4..604ad18c44 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -104,7 +104,7 @@ public: WORLD_TRANSFORM, // 16 | instance of the Transform component REVERSED_WINDING_ORDER, // 1 | det(WORLD_TRANSFORM)<0 VISIBILITY_STATE, // 1 | visibility data of the component - BONES_UBH, // 4 | bones uniform buffer handle + SKINNING_BUFFER, // 8 | bones uniform buffer handle, count, offset WORLD_AABB_CENTER, // 12 | world-space bounding box center of the renderable VISIBLE_MASK, // 1 | each bit represents a visibility in a pass MORPH_WEIGHTS, // 4 | floats for morphing @@ -126,7 +126,7 @@ public: math::mat4f, // WORLD_TRANSFORM bool, // REVERSED_WINDING_ORDER FRenderableManager::Visibility, // VISIBILITY_STATE - backend::Handle, // BONES_UBH + FRenderableManager::SkinningBindingInfo, // SKINNING_BUFFER math::float3, // WORLD_AABB_CENTER VisibleMaskType, // VISIBLE_MASK math::float4, // MORPH_WEIGHTS diff --git a/filament/src/details/SkinningBuffer.h b/filament/src/details/SkinningBuffer.h new file mode 100644 index 0000000000..1f47219739 --- /dev/null +++ b/filament/src/details/SkinningBuffer.h @@ -0,0 +1,80 @@ +/* + * 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_SKINNINGBUFFER_H +#define TNT_FILAMENT_DETAILS_SKINNINGBUFFER_H + +#include "upcast.h" + +#include + +#include "private/filament/EngineEnums.h" + +#include + +#include + +// for gtest +class FilamentTest_Bones_Test; + +namespace filament { + +struct PerRenderableUibBone; + +class FEngine; +class FRenderableManager; + +class FSkinningBuffer : public SkinningBuffer { +public: + FSkinningBuffer(FEngine& engine, const Builder& builder); + + // frees driver resources, object becomes invalid + void terminate(FEngine& engine); + + void setBones(FEngine& engine, RenderableManager::Bone const* transforms, size_t count, size_t offset); + void setBones(FEngine& engine, math::mat4f const* transforms, size_t count, size_t offset); + size_t getBoneCount() const noexcept { return mBoneCount; } + + static size_t getPhysicalBoneCount(size_t count) noexcept { + return (count + CONFIG_MAX_BONE_COUNT - 1) & ~(CONFIG_MAX_BONE_COUNT - 1); + } + +private: + friend class ::FilamentTest_Bones_Test; + friend class SkinningBuffer; + friend class FRenderableManager; + + static void makeBone(PerRenderableUibBone* out, math::mat4f const& transforms) noexcept; + + static void setBones(FEngine& engine, backend::Handle handle, + RenderableManager::Bone const* transforms, size_t boneCount, size_t offset) noexcept; + + static void setBones(FEngine& engine, backend::Handle handle, + math::mat4f const* transforms, size_t boneCount, size_t offset) noexcept; + + backend::Handle getHwHandle() const noexcept { + return mHandle; + } + + backend::Handle mHandle; + uint32_t mBoneCount; +}; + +FILAMENT_UPCAST(SkinningBuffer) + +} // namespace filament + +#endif //TNT_FILAMENT_DETAILS_SKINNINGBUFFER_H diff --git a/filament/test/filament_test.cpp b/filament/test/filament_test.cpp index 75103d99bc..1e01f8386a 100644 --- a/filament/test/filament_test.cpp +++ b/filament/test/filament_test.cpp @@ -792,7 +792,7 @@ TEST(FilamentTest, Bones) { static void check(mat4f const& m) noexcept { PerRenderableUibBone b; - FRenderableManager::makeBone(&b, m); + FSkinningBuffer::makeBone(&b, m); expect_eq(Shader::vertice(b), m); @@ -803,7 +803,7 @@ TEST(FilamentTest, Bones) { static void check(mat4f const& m, float3 const& v) noexcept { PerRenderableUibBone b; - FRenderableManager::makeBone(&b, m); + FSkinningBuffer::makeBone(&b, m); expect_eq((m * v).xyz, Shader::vertice(v, b));