记一个Adreno平台下GPU精度造成的问题

记一个Adreno平台下GPU精度造成的问题

项目中有一个美术需求是这样的,使用时间作为参数控制uv,采样一张Texture,达到看起来流动的效果。OK,看上去很简单,动手实现一下。

Naive Implementation

half2 uv = _Time.g * speed + offset;
half4 col = tex2D(_GlowTex, uv);

很好实现,不过运行起来却遇到了问题,在Adreno平台下(小米6),时间久了特效就会卡顿住,在帧率还是30帧的情况下,特效看起来只有3-4帧的样子,一看就是由于_Time.g太大之后导致浮点数精度不足导致的问题。

Better Implementation

float2 uv = _Time.g * speed + offset;
half4 col = tex2D(_GlowTex, uv + i.uv);

Problem Solved!然而当我在真机上确认时,发现还是出现了卡顿的情况,吓得我连忙确认是不是Shader没有重新编译(Unity一些版本如果只改cginc文件,shader不会重新编译),然而查下来确实用的是最新的shader。

Dig Deeper

这一定是Unity Shader Compiler(HLSLcc)的锅,来翻翻HLSLcc生成的目标平台shader代码,安卓平台下就是glsl。

#version 300 es
precision highp float;
...
vec2 u_xlat5;
...
u_xlat5.xy = _Time.g * speed.xy + offset.xy;
u_xlat16_5.xy = texture(_GlowTex, u_xlat5.xy).xw;
...

崩溃,分明是用的highp精度的浮点数作为uv,为什么结果还是不对?

Solve the Problem?

问题最终的解决,来自我胡乱写了一行代码

float time = fmod(_Time.g, 2e5);
float2 uv = time * speed + offset;
half4 col = tex2D(_GlowTex, uv + i.uv);

这样做了之后,问题奇迹般的解决了!但是为什么呢?首先glsl中没有fmod这个intrinsic,HLSLcc自己实现了一份,大概就是乘上一个数的倒数计算后再乘上这个数:

    u_xlat0.x = _Time.g * 4.99999987e-06;
#ifdef UNITY_ADRENO_ES3
    u_xlatb5 = !!(u_xlat0.x>=(-u_xlat0.x));
#else
    u_xlatb5 = u_xlat0.x>=(-u_xlat0.x);
#endif
    u_xlat0.x = fract(abs(u_xlat0.x));
    u_xlat0.x = (u_xlatb5) ? u_xlat0.x : (-u_xlat0.x);
    u_xlat0.x = u_xlat0.x * 200000.0;

下面就是我猜测的原因了(手边暂时没有方法验证,有办法的大佬请告诉我):

  1. Adreno的shader compiler把fmod计算放到vertex shader中进行了,或者干脆没放到shader中。因为这个计算的结果每个fragment都是一样的,没必要每个fragment都算一次,而计算这个值的地方,不一定用的highp精度的浮点数。
  2. Adreno的shader compiler根本没准守glsl精度的规范。

近一两年Adreno平台经常发生一些bug,之前也遇到shader代码太长导致的花屏问题,再这样下去被mali超过也说不定。

posted @ 2020-03-08 21:52  马子哥  阅读(830)  评论(0编辑  收藏  举报