hey Jeremy thanks for the info.
I am a bit stuck with this, see if you can help, I went of the rute of using the InstancePayloadExtension, I think I got almost everything correctly but I can’t manage to make it work, pretty sure I am missing something, lets go through my changes:
First I added the following fields into the InstancedStaticmeshComponent.h:
UPROPERTY(VisibleAnywhere, Category = "Mesh Painting")
TArray<TObjectPtr<UTexture>> InstancedMeshPaintTextures;
UPROPERTY(SkipSerialization)
TArray<FVector4f> PerInstanceSMPayloadData;
UFUNCTION(BlueprintCallable, Category="Mesh Painting")
ENGINE_API void SetInstanceVirtualTextures(const TArray<UTexture*>& InInstanceTextures);
ENGINE_API virtual void GetUsedTextures(TArray<UTexture*>& OutTextures, EMaterialQualityLevel::Type QualityLevel) override;
On the cpp I added my own function to the ChangeSet, right after setting the CustomData, inside the BuildInstanceDataDelatChangeSetCommon function like this:
ChangeSet.SetPayloadExtensionData(MakeArrayView(PerInstanceSMPayloadData));
Then on the BuildComponentInstanceDaata I am enabling the bHasPerInstancePayloadExtension flag is we have a texture into our array:
OutData.Flags.bHasPerInstancePayloadExtension = !InstancedMeshPaintTextures.IsEmpty();
My function to fill up the descriptors is very basic:
void UInstancedStaticMeshComponent::SetInstanceVirtualTextures(const TArray<UTexture*>& InInstanceTextures)
{
if (InInstanceTextures.IsEmpty())
{
return;
}
InstancedMeshPaintTextures = InInstanceTextures;
TArray<FTextureResource*> InstancedTextureResources;
InstancedTextureResources.Reserve(GetNumInstances());
for (int32 i = 0; i < GetNumInstances(); i++)
{
if (InstancedMeshPaintTextures.IsValidIndex(i) &&
InstancedMeshPaintTextures[i] &&
InstancedMeshPaintTextures[i]->GetResource() &&
InstancedMeshPaintTextures[i]->IsVirtualTexturingEnabled())
{
InstancedTextureResources.Add(InstancedMeshPaintTextures[i]->GetResource());
UE_LOG(LogStaticMesh, Verbose, TEXT("Assigning virtual texture: %s to instance: %i"), *InstancedMeshPaintTextures[i]->GetFName().ToString(), i);
continue;
}
InstancedTextureResources.Add(nullptr);
}
int32 MeshPaintIndex = GetMeshPaintTextureCoordinateIndex();
ENQUEUE_RENDER_COMMAND(FInstancedStaticMeshComponentUpdatePayloadData)(
[WeakThis = MakeWeakObjectPtr(this), InstancedTextureResources, MeshPaintIndex](FRHICommandListImmediate&)
{
TArray<FVector4f> DescriptorsAsPayLoad;
DescriptorsAsPayLoad.Reserve(InstancedTextureResources.Num());
for (FTextureResource* Resource : InstancedTextureResources)
{
FUintVector2 VirtualTextureDescriptor = MeshPaintVirtualTexture::GetTextureDescriptor(Resource, MeshPaintIndex);
DescriptorsAsPayLoad.Add(FVector4f(
static_cast<float>(VirtualTextureDescriptor.X),
static_cast<float>(VirtualTextureDescriptor.Y),
0.0f,
0.0));
UE_LOG(LogTemp, Log, TEXT("Size: %f , %f"), static_cast<float>(VirtualTextureDescriptor.X), static_cast<float>(VirtualTextureDescriptor.Y));
}
AsyncTask(ENamedThreads::GameThread, [WeakThis, DescriptorsAsPayLoad]()
{
WeakThis->PerInstanceSMPayloadData = DescriptorsAsPayLoad;
WeakThis->MarkRenderStateDirty();
});
});
}
void UInstancedStaticMeshComponent::GetUsedTextures(TArray<UTexture*>& OutTextures, EMaterialQualityLevel::Type QualityLevel)
{
Super::GetUsedTextures(OutTextures, QualityLevel);
for(UTexture *InstancedMeshPaintTexture : InstancedMeshPaintTextures)
{
if(InstancedMeshPaintTexture && InstancedMeshPaintTexture->IsVirtualTexturingEnabled())
{
OutTextures.AddUnique(InstancedMeshPaintTexture);
}
}
}
Then on the FISMInstanceUpdateChangeSet class I added my own function to pass the data holded on the component:
void FISMInstanceUpdateChangeSet::SetPayloadExtensionData(const TArrayView<const FVector4f>& InPerInstancePayloadData)
{
GetPayloadExtensionDataWriter().Gather(InPerInstancePayloadData);
}
On the ISMInstanceDataManager.cpp I added the extra float necessary to the payload data stride:
int32 NumPayLoadExtensionFloat4S = 0;
if (ChangeSet.Flags.bHasPerInstancePayloadExtension)
{
NumPayLoadExtensionFloat4S++;
}
InstanceDataBufferHeader.PayloadDataStride = FInstanceSceneDataBuffers::CalcPayloadDataStride(ChangeSet.Flags, NumCustomDataFloats, NumPayLoadExtensionFloat4S);
Once the data I created a custom hlsl node inside the material editor just to verify the data is correctly hooked and I am using the debug floatx node, the info seems to be right, here is the custom hlsl code:
#if IS_NANITE_PASS
FInstanceSceneData InstanceData = GetInstanceSceneData(Parameters.InstanceId);
float4 PayloadExtensionData = LoadInstancePayloadExtensionElement(InstanceData, 0);
return PayloadExtensionData.xyz;
#else
return 0;
#endif
The last step does not seem to be working, I did modify the HLSLMaterialTranslator.cpp to use my custom hlsl functions on the MaterialTemplate.ush:
uint2 ResolveMeshPaintTextureDescriptor(FMaterialPixelParameters Parameters)
{
#if USE_INSTANCING && IS_NANITE_PASS
return GetInstancedMeshPaintTextureDescriptor(GetInstanceSceneData(Parameters.InstanceId));
#else
return GetMeshPaintTextureDescriptor(GetPrimitiveData(Parameters));
#endif
}
uint2 GetInstancedMeshPaintTextureDescriptor(FInstanceSceneData InstanceData)
{
float4 PayloadExtensionData = LoadInstancePayloadExtensionElement(InstanceData, 0);
return uint2(asuint(PayloadExtensionData.x), asuint(PayloadExtensionData.y));
}
Not sure what I am doing wrong at this point, a little orientation will be great
[Attachment Removed]