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) {