how to fix maximum ps_4_0 sampler register index (16) exceeded?

#if ENABLE_SPLATMAP
            fixed4 s = tex2D (_SplatMap, IN.uv_MainTex);
            fixed4 splat = fixed4(s.r * tex2D(_Splat0, IN.uv_MainTex*_SplatScale).rgb, s.r);
            splat += fixed4(s.g * tex2D(_Splat1, IN.uv_MainTex*_SplatScale).rgb, s.g);
            splat += fixed4(s.b * tex2D(_Splat2, IN.uv_MainTex*_SplatScale).rgb, s.b);
            splat += fixed4(s.a * tex2D(_Splat3, IN.uv_MainTex*_SplatScale).rgb, s.a);
            //c = lerp(c, splat, 1-_Color.a)*0.5;
            c += splat;
            splatB = s.r * UnpackNormal(tex2D (_SplatBump0, IN.uv_MainTex*_SplatScale));
            splatB += s.g * UnpackNormal(tex2D (_SplatBump1, IN.uv_MainTex*_SplatScale));
            splatB += s.b * UnpackNormal(tex2D (_SplatBump2, IN.uv_MainTex*_SplatScale));
            splatB += s.a * UnpackNormal(tex2D (_SplatBump3, IN.uv_MainTex*_SplatScale));
            //b = lerp(b, splatB, 1-_Color.a);
            b = lerp(splatB, fixed3(0, 0, 1), -_SplatBumpStrength + 1);
            #endif

if i use c += splat; or b = lerp(b, splatB, 1-_Color.a);
i get the error

maximum ps_4_0 sampler register index (16) exceeded

if i use c = splat;
i don’t get the error?

so what is the solution exactly? a Texture2DArray? could i use it to put the splat map textures in?

@Atair 's quote is probably the most important bit there. My comment about Texture2DArrays was more a comment on using a Texture3D.

Using a texture array is an option, but using no-sampler textures is generally a little more straight forward as it only requires shader code modifications. Here’s the document he was quoting:

See the UNITY_DECLARE_TEX2D and related macros mentioned on that page.

Using a Texture2DArray could plausibly be a little more efficient on newer hardware, and gives you a limit of 2048 textures per array. DX11 has a limit of 128 texture objects per shader, so you could theoretically pass 262144 textures to your shader with texture arrays. However it’s important to note that the number of samplers is still limited to 16. Basically when you sample from a texture array you’re likely using the same sampler every time. A sampler is a physical bit of silicon on the GPU that the shader is accessing. Reading 8 textures with 8 samplers will cause the textures to be sampled in parallel where as sampling 8 textures with 1 sampler is done in serial as each texture effectively has to wait their turn. Modern GPUs are incredibly fast and very good at hiding the time it takes to sample a texture, but if you pile them all onto one sampler unit it’s likely to cause a stall in your shader computation that can show up as a higher frame render time and potentially a lower frame rate.

If you’re targeting OpenGL ES 2.0, texture arrays and separate samplers are out of the question. Your options are texture atlases, multiple passes (which is what Unity’s terrain shader does), or abusing cube maps to store 6 textures as a proto-texture array.

wauw thanks again @bgolus the cubemap method sound smart and easy i’ll try that one first :wink:

This part has confused me because, how could it possibly do this in a situation where the next sampler line depends on variable information taken from a previous line’s sampled info? Or is that an deoptimization you have to be aware of if you’re feeding your sampler any info that isn’t coming directly from the vertex function?

The shader stalls in this case until the sampler returns the data.

This is always what happens if you sample a texture and then use the output to control another texture sample, and has nothing to do with multiple or single samplers. If you can you want to do the initial sample as early as possible and any additional math that’s not dependent on either in between. Some recent desktop GPUs can do some juggling where it’ll start the shader code for next group of pixels while waiting for the sampler units to finish, but don’t expect that. How long a texture takes to “sample” is highly variable, so how many instructions to put between the first and second texture sample isn’t a fixed number. It depends on the texture size, format, GPU memory bandwidth, etc.

So yes, this is a de-optimization you should be aware of. But understand this isn’t necessarily going to add multiple milliseconds to your render time, it’s unlikely to be even a single millisecond. That stall is going to be measured in microseconds for all but the most egregious cases.

1 Like

ok this is what i ended up with:

_Splat0("Splat 0",2D) = "white" {}
        _Splat1("Splat 1",2D) = "white" {}
        _Splat2("Splat 2",2D) = "white" {}
        _SplatBump0("Splat Bump 0",2D) = "bump" {}
        _SplatBump1("Splat Bump 1",2D) = "bump" {}
        _SplatBump2("Splat Bump 2",2D) = "bump" {}
        _SplatScale("Splat Map Tilling",float) = 15
        _SplatBumpStrength("Splat Bump Strength", float) = 2
...
uniform sampler2D _SplatMap;
        uniform sampler2D _MainTex;
        uniform sampler2D _Bump;
UNITY_DECLARE_TEX2D(_Splat0);
        UNITY_DECLARE_TEX2D_NOSAMPLER(_Splat1);
        UNITY_DECLARE_TEX2D_NOSAMPLER(_Splat2);
        UNITY_DECLARE_TEX2D(_SplatBump0);
        UNITY_DECLARE_TEX2D_NOSAMPLER(_SplatBump1);
        UNITY_DECLARE_TEX2D_NOSAMPLER(_SplatBump2);

#if ENABLE_SPLATMAP
            fixed4 s = tex2D (_SplatMap, IN.uv_MainTex);

            fixed4 splat = fixed4(s.r * UNITY_SAMPLE_TEX2D(_Splat0, IN.uv_MainTex*_SplatScale).rgb, s.r);
            splat += fixed4(s.g * UNITY_SAMPLE_TEX2D_SAMPLER(_Splat1,_Splat0, IN.uv_MainTex*_SplatScale).rgb, s.g);
            splat += fixed4(s.b * UNITY_SAMPLE_TEX2D_SAMPLER(_Splat2,_Splat0, IN.uv_MainTex*_SplatScale).rgb, s.b);
            splat += fixed4(s.a * c.rgb, s.a);
            c = splat;
            fixed3 splatB = s.r * UnpackNormal(UNITY_SAMPLE_TEX2D(_SplatBump0, IN.uv_MainTex*_SplatScale));
            splatB += s.g * UnpackNormal(UNITY_SAMPLE_TEX2D_SAMPLER(_SplatBump1,_SplatBump0, IN.uv_MainTex*_SplatScale));
            splatB += s.b * UnpackNormal(UNITY_SAMPLE_TEX2D_SAMPLER(_SplatBump2,_SplatBump0, IN.uv_MainTex*_SplatScale));
            splatB += s.a * UnpackNormal(tex2D(_Bump, IN.uv_MainTex*_SplatScale));
            splatB = lerp(splatB, fixed3(0, 0, 1), -_SplatBumpStrength + 1);
            b = lerp(b, splatB, 1-_Color.a);_SplatBumpStrength + 1);
            #endif