Search Unity

Precision: does Unity just do whatever these days?

Discussion in 'Shaders' started by AcidArrow, Aug 22, 2017.

  1. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,794
    I have a bunch of CG/HLSL shaders. Since I'm in the phase of strongly optimising them for mobile, I started looking more closely to the compiled code and I noticed some weirdness with the precision qualifiers.

    Here's a super simple frag shader:

    Code (csharp):
    1. fixed4 frag(v2f i) : SV_Target
    2. {
    3.    fixed4 c = tex2D(_MainTex, i.uv);
    4.    c.rgb *= DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lmuv));
    5.    return c;
    6. }
    And here is the compiled gles

    Code (csharp):
    1. void main ()
    2. {
    3.    lowp vec4 c_1;
    4.    lowp vec4 tmpvar_2;
    5.    tmpvar_2 = texture2D (_MainTex, xlv_TEXCOORD0);
    6.    c_1.w = tmpvar_2.w;
    7.    mediump vec4 tmpvar_3;
    8.    tmpvar_3 = texture2D (unity_Lightmap, xlv_TEXCOORD1);
    9.    lowp vec4 color_4;
    10.    color_4 = tmpvar_3;
    11.    mediump vec3 tmpvar_5;
    12.    tmpvar_5 = (2.0 * color_4.xyz);
    13.    c_1.xyz = (tmpvar_2.xyz * tmpvar_5);
    14.    gl_FragData[0] = c_1;
    15. }
    You'll notice that it stores the lightmap texture in mediump (?), then casts it to lowp, then doubles it (I'm guessing because of the whole doubldDLR that mobiles use for lightmaps) while putting it in a mediump again, then finally multiplies it with the texture, which is lowp, presumably casting down to it again.

    And... Why? I know most mobile devices these days treat lowp and mediump the same, so it probably doesn't matter, but for some devices it matters. And I can't think of any reason of why it's done this way.

    I also tried storing the lightmap in a half3 variable, in order to save one cast to lowp, but it results in the exact same compiled code.

    Also.

    Let's say that for some reason, I want everything in frag in high precision. It would be pretty stupid thing to do, but let's say I'm doing something weird and I don't care for performance.

    Here's my frag shader. Everything is in highp, including the frag itself. So I'm hoping everything will be kept in the same precision and there will be no casts.

    Code (csharp):
    1. float4 frag(v2f i) : SV_Target
    2. {
    3.    float4 c = tex2D(_MainTex, i.uv);
    4.    float3 lm = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lmuv));
    5.    c.rgb *= lm;
    6.    return c;
    7. }
    Here's the compiled.

    Code (csharp):
    1. void main ()
    2. {
    3.    highp vec3 lm_1;
    4.    highp vec4 c_2;
    5.    lowp vec4 tmpvar_3;
    6.    tmpvar_3 = texture2D (_MainTex, xlv_TEXCOORD0);
    7.    c_2 = tmpvar_3;
    8.    mediump vec4 tmpvar_4;
    9.    tmpvar_4 = texture2D (unity_Lightmap, xlv_TEXCOORD1);
    10.    lowp vec4 color_5;
    11.    color_5 = tmpvar_4;
    12.    mediump vec3 tmpvar_6;
    13.    tmpvar_6 = (2.0 * color_5.xyz);
    14.    lm_1 = tmpvar_6;
    15.    c_2.xyz = (c_2.xyz * lm_1);
    16.    gl_FragData[0] = c_2;
    17. }
    Instead of keeping everything in highp (which is what I was expecting), it does the texture in lowp (??), the lightmap in mediump then in lowp then mediump again and then does a bunch of casts to get to a highp. Which is obviously not what I want.

    It is quite possible I am missing something, but I don't understand why Unity overrides my precision choices and does whatever it wants. And I am pretty sure this wasn't the case in earlier (say, Unity 4 or even 3) versions. Is it a case of Unity deciding they know better (I mean, sure, they probably do) and forced their own precision choices onto everyone? (like that extra blit Android has for compatibility reasons). These things should be an option and should not be forced on everyone. (like a checkbox somewhere that says, "let Unity decide some precisions" or whatever)

    Or, I'm missing something and this is needed and normal...

    Thoughts?
     
    neoshaman likes this.
  2. DreamPower

    DreamPower

    Joined:
    Apr 2, 2017
    Posts:
    103
    I think this is your answer:

    https://developer.apple.com/library..._iOS/OpenGLESPlatforms/OpenGLESPlatforms.html

    And Unity isn't as optimal at dealing with that while converting HLSL into GLES as it could be.
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Unity defines the DecodeLightmap function like this:

    inline half3 DecodeLightmap( fixed4 color )

    That's taking a fixed4 as an input and has a half3 as an output. Your own lm variable is defined as a float3. I don't know why it would be initially sampling the texture as mediump though. If you really want to get rid of most of that, then you'll probably want to override Unity's built in function with your own. Luckily that should be pretty straight forward.

    #if defined(UNITY_NO_RGBM)
    float3 lm = 2.0 * tex2D(unity_Lightmap, i.lmuv).rgb;
    #else
    float3 lm = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lmuv));
    #endif
     
  4. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,794
    I just tried that and it indeed removes the extra cast to lowp and then back to mediump again. (which btw, I am not crazy about doing, since moving away from Unity functions for Unity stuff, means the chances that my shaders will break in the future increases in the case Unity decides to change how they sample the lightmaps, but that's besides the point)

    But, it seems no matter what I do and no matter what precisions I define, normal textures are always initially sampled as lowp and the lightmap is always sampled as mediump.

    So this:
    Code (csharp):
    1.             float4 frag(v2f i) : SV_Target
    2.             {
    3.                 float4 c = tex2D(_YoTex, i.uv);
    4.                 float3 lm = 2.0*UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lmuv).rgb;
    5.                 c.rgb *= lm;              
    6.                 return c;
    7.             }
    (by the way, Unity changes the tex2D to UNITY_SAMPLE_TEX2D as soon as I hit save)
    Turns into:

    Code (csharp):
    1. void main ()
    2. {
    3.    highp vec3 lm_1;
    4.    highp vec4 c_2;
    5.    lowp vec4 tmpvar_3;
    6.    tmpvar_3 = texture2D (_YoTex, xlv_TEXCOORD0);
    7.    c_2 = tmpvar_3;
    8.    mediump vec3 tmpvar_4;
    9.    tmpvar_4 = (2.0 * texture2D (unity_Lightmap, xlv_TEXCOORD1).xyz);
    10.    lm_1 = tmpvar_4;
    11.    c_2.xyz = (c_2.xyz * lm_1);
    12.    gl_FragData[0] = c_2;
    13. }
    So there is no way to keep everything in one precision in the frag shader. Ugh...

    I think that's my issue more or less though. I don't like that Unity is making those precision decisions for me and I like it even less, if those precision decisions don't make a lot of sense (at least to me). It feels like one of those situations where the correct way to handle it would be to produce a bunch of warnings (like : "Performance warning: You probably want those textures to be sampled in lowp!"), so I can be educated, instead of just (sort of) doing it for me and telling me nothing.
     
    neoshaman likes this.
  5. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I've come to the conclusion that trying to optimize precision types in Unity shaders is mostly pointless, because the transcoder/compiler/etc will basically do whatever they want with the type. In general, I just use half for everything that doesn't need to be a full float.
     
    AcidArrow likes this.
  6. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    Desktop gpu's make float out of half and fixed, because it's faster not to convert types. For mobile it can help to use smaller data types. I use a precision include myself. That one defines VALUE as either half or float based one whether PRECISION_HIGH is defined. (I define it for desktop targets and I don't for mobile targets.) And then most of my functions look somewhat like this:
    Code (csharp):
    1.  
    2. VALUE3 function(VALUE input1, VALUE2 input2)
    3.  
    I do a similar thing for FIXED. Then in code, all color variables use FIXED and all others use VALUE. That way I can easily compile for both mobile and desktop while keeping the amount of type conversions to a minimum.