Large texture SetPixel() / Apply() alternative

TLDR: What’s the fastest way you know of updating 1px on a 2048x2048 Texture2d?

I’m using CTS which has a nifty feature where you can use a set of textures on it’s material as splatmaps. That means that instead of going through Unity’s slow slow terrain splat map update at runtime I can make relatively fast terrtain splatmap changes by just updating CTS’ splatmap texture.

So this is already a huge step forward. However my splatmaps are 2048x2048 so even just updating a pixel and applying still takes 8ms (in editor, on a pretty beefy machine) - with 99% of that time on apply(). So that’s much better than the alternatives but I’d still like to push it more if possible - 8ms is half a frame budget and even though I don’t do this every frame it’s still way too much.

From what I read, the alternatives seem to lie with either a compute shader or blit. I’m just wondering if anyone had any experience in this regard and which of the two (or maybe something different) to explore.

Furthermore, my use case is updating just one pixel on a large texture - from what I saw any shader approach targets the whole texture - so it would have to run the shader for each pixel where my shader will be something like if x = px and y = py set color else leave the color that was there before.

Thoughts?

I think the problem is not updating the pixel but upload the new texure to the shader from cpu → gpu each frame.
You can change the pixel by the shader too (this i assume is the fastest) and pass the pixel as shader variable, but in the shader code its hard to find the exact texture pixel position you want, because the shader “dont thinks in pixel”.

So the question is, what are you trying to achieve?

I’m using a terrain libary which uses Texture2ds as splatmaps. I’m trying to edit those textures in the fastest way possible.

The biggest problem I have is the library uses Texture2d while I seem to need a RenderTexture to be able to edit it by shader.

So the question is… is there a way to quickly cast between Texture2d and RenderTexture (in a way that preserves the texture memory area on the GPU)?

does your texture really need to be 2048x2048?

if you find a way to divide your texture into smaller parts you would have a good solution.

apply() is quite fast on small textures

Can’t divide the texture - it’s a splatmap used by a different component. The only way to make it smaller would loose splatmap resolution on the terrain so that’s not a go either :frowning:

But why you only what to replace only 1 pixel on this big splatmap, this doesnt makes any sense or? For painting structures on the terrain you need to change more than only 1 pixel and therefore i would use a shader pass on a render texture where the render texture is holding the original splat map. The shader pass then adds the 2nd texture to the render texture (same as post rendering effects do) and the rendertexture will be passed to the CTS shader.

Well, actually it’s 1px because I’m doing field tilling. So I want to change the square they till from grass to mud. But the problem and the solution seem very similar.

What I ended up doing was to use a compute shader to set that 1 pixel. Now of course the issue is compute shaders work on RenderTextures and cts needs a Texture2d. So I ended up using Graphics.CopyTexture to copy from my rendertex to the tex2d.

What’s more, due to the fact that I have more than 4 terrain textures, CTS uses 2 splatmaps so I had to update both (2xApply() ~ 16 ms). With the compute shader solution (I update both splatmaps with 1 compute call) + 2 x CopyTexture back into CTS’ tex2d … I don’t know how long it takes because I can’t find the frame it does it in the profiler - it’s that fast :slight_smile:

I guess doing it with blit() and a regular shader would have yielded very similar results. Pending testing this on some weaker GPUs (doing it on a 1080ti right now), I think this is a pretty solid solution.

1 Like