Version: 1.7
语言 : 中文
异常处理优化
Math 库 Wasm SIMD 支持

Shader变体异步Warmup

WebGL 不支持 binary shader,因此单个 shader 在编译、链接和检查链接状态等过程中,GL 函数的执行时间较长,可能需要几十到上百毫秒。这导致在调用 ShaderVariantCollection.WarmUp() 方法时,主线程可能会被阻塞数秒。

然而,在支持 KHR_parallel_shader_compile 扩展的浏览器中,我们可以异步执行 ShaderWarmup 。其工作原理是,在调用 glGetProgramiv 以检查链接状态时,使用 GL_COMPLETION_STATUS_KHR 替代 GL_LINK_STATUSGL_COMPLETION_STATUS_KHR 可以立即返回状态结果( 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()
异常处理优化
Math 库 Wasm SIMD 支持