Texture-Sampled Inverse Transform Matrix Always Behind Scene Proxy → Visible Texture “Sliding” When Actor Moves

Hello everyone,

I’m currently implementing a system where each mesh in my scene receives a unique stencil ID, and I upload a corresponding inverse transform matrix into a float texture lookup table (256×3, PF_A32B32G32R32F).
The goal is to sample this matrix inside a material (and later, a post-process material), reconstruct the object’s local position using AbsoluteWorldPosition, and use that for projection. (See blueprint and material setups images below)

What works:

When the actor is static, the projection is pixel-perfect and matches Unreal’s built-in TransformPosition material node 1:1.
This confirms that the texture format, encoding, decoding, and reconstruction are correct.

The problem:

When the actor moves, my matrix-based projection lags behind the built-in TransformPosition by one frame.
This creates a very noticeable texture “sliding” effect proportional to movement speed:

  • The faster the actor moves, the more the texture appears to offset.

  • The projection is otherwise correct,just temporally out of sync.

It looks exactly like the world position comes from the current frame, but the matrix in my texture comes from the previous one.

Why this is strange

I upload the texture immediately after writing the matrix:

void UStencilMatrixLUTSubsystem::UploadIfDirty()
{
	if (!bDirty || !MatrixLUTTexture)
		return;

	FTexture2DResource* TexResource =
		static_cast<FTexture2DResource*>(MatrixLUTTexture->GetResource());
	if (!TexResource)
		return;

	const uint32 Width = LUT_Width;
	const uint32 Height = LUT_Height;

	// Copy to a local buffer captured by value so the render thread sees
	// stable memory
	TArray<FLinearColor> LocalPixelData = PixelData;

	ENQUEUE_RENDER_COMMAND(UpdateStencilMatrixLUT)(
		[TexResource, LocalPixelData, Width, Height](FRHICommandListImmediate& RHICmdList)
		{
			FUpdateTextureRegion2D Region(0, 0, 0, 0, Width, Height);

			RHICmdList.UpdateTexture2D(
				TexResource->GetTexture2DRHI(),
				0,                         // mip
				Region,
				Width * sizeof(FLinearColor),
				(uint8*)LocalPixelData.GetData());
		});
	//FlushRenderingCommands();
	bDirty = false;
}

I even experimented with:

  • triggering uploads immediately (no Tick delay),

  • removing/enabling FlushRenderingCommands(),

  • verifying that the LUT texture receives new matrix values every frame.

Despite this, the material always reads a matrix one or a few frames behind the actual rendered transform, causing drifting.

Understanding the root cause (I think)

After digging deeper, I now suspect the following:

  • The render thread renders meshes using the scene proxy’s transform T(N-1).

  • My subsystem runs on the game thread, where I compute inverse matrices using T(N).

  • I upload T(N) into the LUT texture.

  • The render thread then samples world position from T(N-1) but samples matrix T(N).

This mismatch (current matrix vs previous proxy transform) would perfectly explain the sliding.

My question

Is there any way to synchronize a CPU-generated texture with the same-frame scene proxy transform that materials use?

Or more specifically:

  • Can a UWorldSubsystem or FTickableGameObject push GPU texture data early enough for the same-frame material evaluation?

  • Is there a mechanism to update a texture from the render thread itself using the scene proxy’s matrices?

  • Is the render thread/game thread transform delay an unavoidable limitation unless I write a custom FPrimitiveSceneProxy or FSceneViewExtension to generate the LUT entirely on the render thread?

Extra details

  • Texture format: PF_A32B32G32R32F

  • Texture is transient, no mipmaps, clamp, nearest filtering

  • Texture is sampled directly in a regular material (not yet post-process)

  • Problem reproduces even with FlushRenderingCommands() (used only for testing)

Before I re-architect this whole system around FSceneViewExtension, any insight from Epic staff or experienced engine programmers would be greatly appreciated.
Thank you!