Dynamic Material Instances are the only option if you are creating the texture at runtime. Alternatively, you can create a render target asset as you say and have all materials point to that. That’s arguably the most efficient way for a very widely-used texture source.
In terms of updating the render target, it’s no different to any other texture. You can still resize it and draw stuff into it however you like. You just need a reference to it, the same way as any other asset.
As for updating it, there are a few different ways - but I guess it depends on what you’re doing. UE has the “Draw Material To Render Target” system which is pretty nice, or if you want to write pixel values directly, you can use a render command.
Here’s a copy-paste from some of my gamecode. In this case, this is a HDR Render Target and ‘RenderData’ is a TArray<FFloat16Color> of equal size, and matching memory layout. I’m quite literally copying the array into the render target.
const uint32 NumBytes = CalculateImageBytes(RenderTarget->SizeX, RenderTarget->SizeY, 0, RenderTarget->GetFormat());
[Data = RenderData, Bytes = NumBytes, LUT = RenderTarget->Resource->TextureRHI, bFlush = bFlushRendering](FRHICommandListImmediate& RHICmdList)
uint32 DestStride = 0;
FFloat16Color* DestBuffer = static_cast<FFloat16Color*>(RHILockTexture2D(LUT->GetTexture2D(), 0, RLM_WriteOnly, DestStride, false, false));
FMemory::Memcpy(DestBuffer, Data.GetData(), Bytes);
RHIUnlockTexture2D(LUT->GetTexture2D(), 0, false, bFlush);
Be careful what you pass between render and game thread however. I’m passing data by copying it here, but if you want to reference an object on the render thread or something, you need to use a render fence or somesuch:
The code above is pretty fast, I’ve not had any issues doing this yet. The real slowdowns come when you do something that calls FlushRenderingCommands(), which blocks Game Thread execution until the rendering thread is done. It’s not really acceptable to use that outside of the editor environment.