From 1d6aeb5de1d9b6dcd355a32965ed5e8063ecff33 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 14 Feb 2022 13:55:59 -0800 Subject: [PATCH] Add basic support for instanced renderables We can now specify an "instance count" per renderable during creation. This instance count applies to all render primitives in the Renderable. This will simply draw the renderable "instance count" times. A new method available to vertex shaders, getInstanceIndex(), allows the material to discriminate the instances and adjust the position/transform accordingly. Typically this is used for particle systems. --- .../src/main/cpp/RenderableManager.cpp | 7 ++++++ .../android/filament/RenderableManager.java | 17 ++++++++++++++ filament/include/filament/RenderableManager.h | 12 ++++++++++ filament/src/RenderPass.cpp | 5 ++++- filament/src/RenderPass.h | 3 ++- filament/src/Scene.cpp | 1 + filament/src/components/RenderableManager.cpp | 7 ++++++ filament/src/components/RenderableManager.h | 22 +++++++++++++------ filament/src/details/Scene.h | 2 ++ shaders/src/getters.vs | 9 ++++++++ 10 files changed, 76 insertions(+), 9 deletions(-) diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index 0e7953a3c2..8276692457 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -221,6 +221,13 @@ Java_com_google_android_filament_RenderableManager_nBuilderLightChannel(JNIEnv*, builder->lightChannel(channel, (bool)enable); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nBuilderInstances(JNIEnv*, jclass, + jlong nativeBuilder, jint instanceCount) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + builder->instances(instanceCount); +} + // ------------------------------------------------------------------------------------------------ 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 ca8240a626..6858ce54d8 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 @@ -284,6 +284,22 @@ public class RenderableManager { return this; } + /** + * Specifies the number of draw instance of this renderable. The default is 1 instance and + * the maximum number of instances allowed is 65535. 0 is invalid. + * All instances are culled using the same bounding box, so care must be taken to make + * sure all instances render inside the specified bounding box. + * The material can use getInstanceIndex() in the vertex shader to get the instance index and + * possibly adjust the position or transform. + * + * @param instanceCount the number of instances silently clamped between 1 and 65535. + */ + @NonNull + public Builder instances(@IntRange(from = 1, to = 65535) int instanceCount) { + nBuilderInstances(mNativeBuilder, instanceCount); + return this; + } + /** * Controls if this renderable casts shadows, false by default. * @@ -811,6 +827,7 @@ public class RenderableManager { private static native void nBuilderMorphing(long nativeBuilder, int targetCount); private static native void nEnableSkinningBuffers(long nativeBuilder, boolean enabled); private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable); + private static native void nBuilderInstances(long nativeRenderableManager, int instances); 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); diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 0d7b27ccd1..0bb37a9c02 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -328,6 +328,18 @@ public: */ Builder& blendOrder(size_t primitiveIndex, uint16_t order) noexcept; + /** + * Specifies the number of draw instance of this renderable. The default is 1 instance and + * the maximum number of instances allowed is 65535. 0 is invalid. + * All instances are culled using the same bounding box, so care must be taken to make + * sure all instances render inside the specified bounding box. + * The material can use getInstanceIndex() in the vertex shader to get the instance index and + * possibly adjust the position or transform. + * + * @param instanceCount the number of instances silently clamped between 1 and 65535. + */ + Builder& instances(size_t instanceCount) noexcept; + /** * Adds the Renderable component to an entity. * diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 36cc155521..5eb60baab5 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -291,6 +291,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, auto const* const UTILS_RESTRICT soaPrimitives = soa.data(); auto const* const UTILS_RESTRICT soaMorphing = soa.data(); auto const* const UTILS_RESTRICT soaVisibilityMask = soa.data(); + auto const* const UTILS_RESTRICT soaInstanceCount = soa.data(); const bool hasShadowing = renderFlags & HAS_SHADOWING; const bool viewInverseFrontFaces = renderFlags & HAS_INVERSE_FRONT_FACES; @@ -357,6 +358,7 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, cmdColor.key = makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT); cmdColor.primitive.index = (uint16_t)i; + cmdColor.primitive.instanceCount = soaInstanceCount[i]; // if we are already a SSR variant, the SRE bit is already set, // there is no harm setting it again @@ -371,6 +373,7 @@ 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.instanceCount = soaInstanceCount[i]; cmdDepth.primitive.materialVariant.setSkinning( soaVisibility[i].skinning || soaVisibility[i].morphing); cmdDepth.primitive.rasterState.inverseFrontFaces = inverseFrontFaces; @@ -621,7 +624,7 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, } } - driver.draw(pipeline, info.primitiveHandle, 1); + driver.draw(pipeline, info.primitiveHandle, info.instanceCount); } } } diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index d4a3b96bf5..bf4fee7410 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -220,8 +220,9 @@ public: backend::Handle morphTargetBuffer; // 4 bytes backend::RasterState rasterState; // 4 bytes uint16_t index = 0; // 2 bytes + uint16_t instanceCount; // 2 bytes Variant materialVariant; // 1 byte - uint8_t reserved[13 - sizeof(void*)] = {}; // 5 bytes (9) + uint8_t reserved[11 - sizeof(void*)] = {}; // 3 bytes (7) }; static_assert(sizeof(PrimitiveInfo) == 32); diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 67c453d766..fee779dd74 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -142,6 +142,7 @@ void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCa worldAABB.center, // WORLD_AABB_CENTER 0, // VISIBLE_MASK rcm.getChannels(ri), // CHANNELS + rcm.getInstanceCount(ri), // INSTANCE_COUNT rcm.getLayerMask(ri), // LAYERS worldAABB.halfExtent, // WORLD_AABB_EXTENT {}, // PRIMITIVES diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 516d26309f..822af6b718 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -49,6 +49,7 @@ struct RenderableManager::BuilderDetails { uint8_t mLayerMask = 0x1; uint8_t mPriority = 0x4; uint8_t mChannels = 1; + uint16_t mInstanceCount = 1; bool mCulling : 1; bool mCastShadows : 1; bool mReceiveShadows : 1; @@ -275,6 +276,11 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng return Success; } +RenderableManager::Builder& RenderableManager::Builder::instances(size_t instanceCount) noexcept { + mImpl->mInstanceCount = clamp((unsigned int)instanceCount, 1u, 65535u); + return *this; +} + // ------------------------------------------------------------------------------------------------ FRenderableManager::FRenderableManager(FEngine& engine) noexcept : mEngine(engine) { @@ -319,6 +325,7 @@ void FRenderableManager::create( setSkinning(ci, false); setMorphing(ci, builder->mMorphTargetCount); mManager[ci].channels = builder->mChannels; + mManager[ci].instanceCount = builder->mInstanceCount; const uint32_t boneCount = builder->mSkinningBoneCount; const uint32_t targetCount = builder->mMorphTargetCount; diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 798fc889ec..ac122d3264 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -129,6 +129,7 @@ public: inline uint8_t getLayerMask(Instance instance) const noexcept; inline uint8_t getPriority(Instance instance) const noexcept; inline uint8_t getChannels(Instance instance) const noexcept; + inline uint16_t getInstanceCount(Instance instance) const noexcept; struct SkinningBindingInfo { backend::Handle handle; @@ -187,6 +188,7 @@ private: LAYERS, // user data MORPH_WEIGHTS, // filament data, UBO storing a pointer to the morph weights information CHANNELS, // user data + INSTANCE_COUNT, // user data VISIBILITY, // user data PRIMITIVES, // user data BONES, // filament data, UBO storing a pointer to the bones information @@ -197,6 +199,7 @@ private: uint8_t, // LAYERS MorphWeights, // MORPH_WEIGHTS uint8_t, // CHANNELS + uint16_t, // INSTANCE_COUNT Visibility, // VISIBILITY utils::Slice, // PRIMITIVES Bones // BONES @@ -214,13 +217,14 @@ private: union { // this specific usage of union is permitted. All fields are identical - Field aabb; - Field layers; - Field morphWeights; - Field channels; - Field visibility; - Field primitives; - Field bones; + Field aabb; + Field layers; + Field morphWeights; + Field channels; + Field instanceCount; + Field visibility; + Field primitives; + Field bones; }; }; @@ -343,6 +347,10 @@ uint8_t FRenderableManager::getChannels(Instance instance) const noexcept { return mManager[instance].channels; } +uint16_t FRenderableManager::getInstanceCount(Instance instance) const noexcept { + return mManager[instance].instanceCount; +} + Box const& FRenderableManager::getAABB(Instance instance) const noexcept { return mManager[instance].aabb; } diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index e85c5397c6..dbba65d531 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -111,6 +111,7 @@ public: WORLD_AABB_CENTER, // 12 | world-space bounding box center of the renderable VISIBLE_MASK, // 2 | each bit represents a visibility in a pass CHANNELS, // 1 | currently light channels only + INSTANCE_COUNT, // 2 | draw instance count // These are not needed anymore after culling LAYERS, // 1 | layers @@ -133,6 +134,7 @@ public: math::float3, // WORLD_AABB_CENTER VisibleMaskType, // VISIBLE_MASK uint8_t, // CHANNELS + uint16_t, // INSTANCE_COUNT uint8_t, // LAYERS math::float3, // WORLD_AABB_EXTENT utils::Slice, // PRIMITIVES diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index bec038b075..1638f69fe4 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -29,6 +29,15 @@ int getVertexIndex() { #endif } +/** @public-api */ +int getInstanceIndex() { +#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) + return gl_InstanceIndex; +#else + return gl_InstanceID; +#endif +} + #if defined(VARIANT_HAS_SKINNING_OR_MORPHING) vec3 mulBoneNormal(vec3 n, uint i) {