Version: 1.3
语言 : 中文
图形渲染优化
提高DynamicVBO对象池复用效率

Shader变体异步Warmup

由于WebGL不支持binary shader,单个shader经过编译、链接、检查链接状态等操作,GL函数耗时较长,通常需要几十到上百毫秒。因此在调用ShaderVariantCollection.WarmUp()时,会让主线程卡住长达数秒的时间。 但在支持KHR_parallel_shader_compile扩展的浏览器上,可以将ShaderWarmup异步化。其基本原理是,调用glGetProgramiv检查链接状态时,用GL_COMPLETION_STATUS_KHR代替GL_LINK_STATUS,前者会立即返回状态结果是TRUEFALSE,而不是等待链接结果再返回。为此,我们添加了异步的接口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,之后就可以处理其他工作。

WarmUpAsync()
WarmUpAsync()

同步方式时,有些CreateGpuProgram函数单次调用就需要181ms,主线程阻塞4633ms之后,才能处理其他工作。

WarmUp()
WarmUp()
图形渲染优化
提高DynamicVBO对象池复用效率