由于WebGL不支持binary shader,单个shader经过编译、链接、检查链接状态等操作,GL函数耗时较长,通常需要几十到上百毫秒。因此在调用ShaderVariantCollection.WarmUp()时,会让主线程卡住长达数秒的时间。
但在支持KHR_parallel_shader_compile扩展的浏览器上,可以将ShaderWarmup异步化。其基本原理是,调用glGetProgramiv检查链接状态时,用GL_COMPLETION_STATUS_KHR代替GL_LINK_STATUS,前者会立即返回状态结果是TRUE或FALSE,而不是等待链接结果再返回。为此,我们添加了异步的接口ShaderVariantCollection.WarmUpAsync(),该接口会对着色器变体集合中配置的变体先进行编译和链接,从返回的AsyncOperation可获得进度和注册完成后的事件处理,之后每帧使用GL_COMPLETION_STATUS_KHR检查,直到所有program都链接完成,再触发completed事件。相比调用同步接口,从开始到完成的总时间相近,但是异步方式允许主线程在warmup期间继续处理其他任务。
示例如下:
public class WarmUpAsyncTest : MonoBehaviour
{
private bool warmupInProcess = false;
private AsyncOperation warmupOp;
private ShaderVariantCollection svc;
void Start()
{
svc = Resources.Load<ShaderVariantCollection>("ShaderVariants");
if (svc)
{
warmupOp = svc.WarmUpAsync();
warmupOp.completed += WarmupOp_completed;
warmupInProcess = true;
}
}
private void WarmupOp_completed(AsyncOperation obj)
{
warmupInProcess = false;
// Enter game scene or do rendering works.
}
void Update()
{
if (warmupInProcess)
{
Debug.Log("----progress: " + warmupOp.progress);
}
}
}
推荐在执行网络请求或数据IO等耗时工作之前调用WarmUpAsync(),收到completed事件后再使用相关shader进行渲染工作,例如进入游戏场景。如果在warmup完成之前就进行渲染工作,用到的shader会退化到同步方式进行编译链接,可能出现卡顿现象。
测试环境: iPad mini 6,A15,iOS 17.5.1,变体集合中包含79个shader变体
NoDevelopment模式下:WarmUpAsync总耗时:3599ms,主线程阻塞时间:125ms,WarmUpSync总耗时:4173ms,主线程阻塞时间4173ms。
Development模式下: 异步方式时,CreateGpuProgram函数很快返回,主线程只阻塞76ms,之后就可以处理其他工作。
同步方式时,有些CreateGpuProgram函数单次调用就需要181ms,主线程阻塞4633ms之后,才能处理其他工作。