WebGL 如何避免长着色器编译停止选项卡
WebGL how to avoid long shader compile stalling a tab
我有一个巨大的着色器,编译需要一分多钟,在这个过程中整个浏览器完全停止运行。据我所知,着色器编译不能异步进行,因此您可以在等待编译完成时 运行 其他 WebGL 命令。
我已经尝试过以下方法:
- 一段时间内不要使用那个特定的着色器 - 这不起作用,因为大多数其他 WebGL 命令将等待它完成,即使那个着色器程序是从未激活
- 使用另一个上下文 - 与上面相同,但即使来自另一个上下文的 WebGL 命令也会导致停顿
- 在 web worker 中使用 OffscreenCanvas - 这也不能避免停顿,即使它在 worker 中,也会使整个浏览器停顿。即使我在命令 link 程序发出任何其他 WebGL 命令后等待几分钟,浏览器也会停止(好像在那段时间什么也没发生)
另一个问题是它有时会导致 WebGL 崩溃(上下文丢失),这会导致页面上(或 worker 中)的所有上下文崩溃。
我能做些什么来避免浏览器停滞吗?
我可以将着色器拆分为多个部分并分别编译它们吗?
这是我的程序初始化的样子,可以更改吗?
let vertexShader = gl.createShader(gl.VERTEX_SHADER);
let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
let program = gl.createProgram();
gl.shaderSource(vertexShader, vertexSource);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
let status = gl.getProgramParameter(program, gl.LINK_STATUS);
let programLog = gl.getProgramInfoLog(program);
调用 link 程序后等待几分钟,即使在 worker 中也无济于事。
最后要注意的是:我可以有例如windows 使用 OpenGL 运行ning 的游戏不受此影响(游戏是 运行ning,我开始在浏览器中编译此着色器,游戏继续 运行 正常,而浏览器停顿)
更新
Chromium 添加了 KHR_parallel_shader_compile 扩展,允许您查询着色器是否已完成编译。
不幸的是,截至 2021 年 1 月,只有 Chromium(Chrome/Edge/Brave/etc...)实现了它
原回答
没有好的解决办法
Windows 上的浏览器使用 DirectX,因为 OpenGL 在许多机器上默认不提供,而且浏览器所需的许多其他功能与 OpenGL 不兼容。
DirectX 编译着色器需要很长时间。只有微软可以解决这个问题。 Microsoft 已提供 HLSL 着色器编译器的源代码,但它仅适用于 DX12。
有些人建议允许网页提供二进制着色器,但由于两个非常重要的原因,这永远不会发生
它们不可携带
一个网页必须提供 100 种或 1000 种二进制着色器的变体。每种类型的 GPU * 每种类型的 driver * 每个平台(iOS、Android、PI、Mac、Windows、Linux , 火, ...)。网页应该随处加载,因此着色器二进制文件不是网络解决方案。
这将是一个巨大的安全问题。
让用户下载提供给 OS/GPU 以执行的随机二进制 blob 将是巨大的漏洞利用来源。1
请注意,某些浏览器(尤其是 Chrome)会在后台本地缓存着色器二进制文件,但这对首次编译没有帮助。
所以目前基本上没有解决办法。您可以制作更简单的着色器或一次编译更少的着色器。人们已经要求一个异步扩展来编译着色器,但没有任何进展。
这是 2 年前的话题
https://www.khronos.org/webgl/public-mailing-list/public_webgl/1702/msg00039.php
只是个人意见,但我猜想异步扩展没有太多动作的原因是它实现起来比听起来要多得多,而且有很多具有复杂着色器的站点存在并且似乎可以工作。
1您作为文本 GLSL 传递给 WebGL 的着色器由浏览器编译,检查各种问题,如果有任何 WebGL 规则被破坏,它们将被拒绝,它们是然后 re-written 为了安全,插入了 bug 解决方法,变量名称 re-written,添加了钳位指令,有时循环展开,各种确保你不会崩溃的东西 driver。您可以使用 WEBGL_debug_shaders
扩展来查看实际发送到 driver.
的着色器
二进制着色器是您提供给 driver 的 blob,您没有机会检查它或验证它没有做坏事,因为它是 driver 专有二进制文件。没有关于其中内容和格式的文档,它们可以随每个 GPU 和每个 driver 而改变。你只需要相信 driver。司机不值得信赖。最重要的是它是在您的机器上执行的不受信任的代码。这与下载随机 .exe 并执行它们没有什么不同,因此它不会发生。
至于 WebGPU,不,WebGL 不再存在安全风险。即使它使用二进制格式,二进制格式也将用于 WebGPU 本身,而不是 driver。 WebGPU 将读取二进制文件,检查是否遵循所有规则,然后生成与用户 GPU 相匹配的着色器。生成的着色器可以是 GLSL、HLSL、MetalSL、SPIR-V,无论是否有效,但与 WebGL 类似,它只会在验证所有规则都得到遵循后才编写着色器,然后它编写的着色器,就像 WebGL 一样,将包含变通方法,夹紧和任何其他需要使着色器安全的东西。请注意,截至今天 2018 年 11 月 30 日,WebGPU 的着色器格式尚未确定。 Google 和 Mozilla 正在推动二进制 SPIR-V 的子集,Apple and Microsoft are pushing for WHLSL,文本
中 HLSL 的变体
请注意,当浏览器显示“RATS!WebGL 有问题”时,并不意味着 driver 崩溃了。相反,它几乎总是意味着 GPU 重置时间过长。在 Chrome(不确定其他浏览器)中,当 Chrome 要求 GPU(通过 driver)做某事时,它会启动一个计时器。如果 GPU 没有在 2-5 秒内完成(不确定实际超时),那么 Chrome 将终止 GPU 进程。这包括编译着色器,因为 DirectX 花费最多的时间来编译这就是为什么这个问题在 DirectX 上出现最多的原因。
在 Windows 即使 Chrome 没有这样做 Windows 也会这样做。这主要是因为大多数 GPU(可能全部在 2018 年)无法像 CPU 那样进行多任务处理。这意味着如果你给他们 30 分钟的工作时间,他们会在 30 分钟内不间断地完成工作,这基本上会冻结你的机器,因为你的机器需要 GPU 来绘制应用程序 windows 等。过去 Windows 解决了这个问题,就像 Chrome 一样,如果花费的时间太长,则重置 GPU。过去 Linux 和 Mac 只会冻结那 30 分钟或使 OS 崩溃,因为 OS 期望能够绘制图形但不能到。在过去 8 年的某个时候 Mac 和Linux 在这方面做得更好。在任何情况下,Chrome 都需要积极主动,因此它会使用自己的计时器并在时间过长时将其杀死。
我有一个巨大的着色器,编译需要一分多钟,在这个过程中整个浏览器完全停止运行。据我所知,着色器编译不能异步进行,因此您可以在等待编译完成时 运行 其他 WebGL 命令。
我已经尝试过以下方法:
- 一段时间内不要使用那个特定的着色器 - 这不起作用,因为大多数其他 WebGL 命令将等待它完成,即使那个着色器程序是从未激活
- 使用另一个上下文 - 与上面相同,但即使来自另一个上下文的 WebGL 命令也会导致停顿
- 在 web worker 中使用 OffscreenCanvas - 这也不能避免停顿,即使它在 worker 中,也会使整个浏览器停顿。即使我在命令 link 程序发出任何其他 WebGL 命令后等待几分钟,浏览器也会停止(好像在那段时间什么也没发生)
另一个问题是它有时会导致 WebGL 崩溃(上下文丢失),这会导致页面上(或 worker 中)的所有上下文崩溃。
我能做些什么来避免浏览器停滞吗?
我可以将着色器拆分为多个部分并分别编译它们吗?
这是我的程序初始化的样子,可以更改吗?
let vertexShader = gl.createShader(gl.VERTEX_SHADER);
let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
let program = gl.createProgram();
gl.shaderSource(vertexShader, vertexSource);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
let status = gl.getProgramParameter(program, gl.LINK_STATUS);
let programLog = gl.getProgramInfoLog(program);
调用 link 程序后等待几分钟,即使在 worker 中也无济于事。
最后要注意的是:我可以有例如windows 使用 OpenGL 运行ning 的游戏不受此影响(游戏是 运行ning,我开始在浏览器中编译此着色器,游戏继续 运行 正常,而浏览器停顿)
更新
Chromium 添加了 KHR_parallel_shader_compile 扩展,允许您查询着色器是否已完成编译。
不幸的是,截至 2021 年 1 月,只有 Chromium(Chrome/Edge/Brave/etc...)实现了它
原回答
没有好的解决办法
Windows 上的浏览器使用 DirectX,因为 OpenGL 在许多机器上默认不提供,而且浏览器所需的许多其他功能与 OpenGL 不兼容。
DirectX 编译着色器需要很长时间。只有微软可以解决这个问题。 Microsoft 已提供 HLSL 着色器编译器的源代码,但它仅适用于 DX12。
有些人建议允许网页提供二进制着色器,但由于两个非常重要的原因,这永远不会发生
它们不可携带
一个网页必须提供 100 种或 1000 种二进制着色器的变体。每种类型的 GPU * 每种类型的 driver * 每个平台(iOS、Android、PI、Mac、Windows、Linux , 火, ...)。网页应该随处加载,因此着色器二进制文件不是网络解决方案。
这将是一个巨大的安全问题。
让用户下载提供给 OS/GPU 以执行的随机二进制 blob 将是巨大的漏洞利用来源。1
请注意,某些浏览器(尤其是 Chrome)会在后台本地缓存着色器二进制文件,但这对首次编译没有帮助。
所以目前基本上没有解决办法。您可以制作更简单的着色器或一次编译更少的着色器。人们已经要求一个异步扩展来编译着色器,但没有任何进展。
这是 2 年前的话题 https://www.khronos.org/webgl/public-mailing-list/public_webgl/1702/msg00039.php
只是个人意见,但我猜想异步扩展没有太多动作的原因是它实现起来比听起来要多得多,而且有很多具有复杂着色器的站点存在并且似乎可以工作。
1您作为文本 GLSL 传递给 WebGL 的着色器由浏览器编译,检查各种问题,如果有任何 WebGL 规则被破坏,它们将被拒绝,它们是然后 re-written 为了安全,插入了 bug 解决方法,变量名称 re-written,添加了钳位指令,有时循环展开,各种确保你不会崩溃的东西 driver。您可以使用 WEBGL_debug_shaders
扩展来查看实际发送到 driver.
二进制着色器是您提供给 driver 的 blob,您没有机会检查它或验证它没有做坏事,因为它是 driver 专有二进制文件。没有关于其中内容和格式的文档,它们可以随每个 GPU 和每个 driver 而改变。你只需要相信 driver。司机不值得信赖。最重要的是它是在您的机器上执行的不受信任的代码。这与下载随机 .exe 并执行它们没有什么不同,因此它不会发生。
至于 WebGPU,不,WebGL 不再存在安全风险。即使它使用二进制格式,二进制格式也将用于 WebGPU 本身,而不是 driver。 WebGPU 将读取二进制文件,检查是否遵循所有规则,然后生成与用户 GPU 相匹配的着色器。生成的着色器可以是 GLSL、HLSL、MetalSL、SPIR-V,无论是否有效,但与 WebGL 类似,它只会在验证所有规则都得到遵循后才编写着色器,然后它编写的着色器,就像 WebGL 一样,将包含变通方法,夹紧和任何其他需要使着色器安全的东西。请注意,截至今天 2018 年 11 月 30 日,WebGPU 的着色器格式尚未确定。 Google 和 Mozilla 正在推动二进制 SPIR-V 的子集,Apple and Microsoft are pushing for WHLSL,文本
中 HLSL 的变体请注意,当浏览器显示“RATS!WebGL 有问题”时,并不意味着 driver 崩溃了。相反,它几乎总是意味着 GPU 重置时间过长。在 Chrome(不确定其他浏览器)中,当 Chrome 要求 GPU(通过 driver)做某事时,它会启动一个计时器。如果 GPU 没有在 2-5 秒内完成(不确定实际超时),那么 Chrome 将终止 GPU 进程。这包括编译着色器,因为 DirectX 花费最多的时间来编译这就是为什么这个问题在 DirectX 上出现最多的原因。
在 Windows 即使 Chrome 没有这样做 Windows 也会这样做。这主要是因为大多数 GPU(可能全部在 2018 年)无法像 CPU 那样进行多任务处理。这意味着如果你给他们 30 分钟的工作时间,他们会在 30 分钟内不间断地完成工作,这基本上会冻结你的机器,因为你的机器需要 GPU 来绘制应用程序 windows 等。过去 Windows 解决了这个问题,就像 Chrome 一样,如果花费的时间太长,则重置 GPU。过去 Linux 和 Mac 只会冻结那 30 分钟或使 OS 崩溃,因为 OS 期望能够绘制图形但不能到。在过去 8 年的某个时候 Mac 和Linux 在这方面做得更好。在任何情况下,Chrome 都需要积极主动,因此它会使用自己的计时器并在时间过长时将其杀死。