Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[g-webgl] HAL 适配 WebGL1 #851

Closed
xiaoiver opened this issue Jan 13, 2022 · 0 comments
Closed

[g-webgl] HAL 适配 WebGL1 #851

xiaoiver opened this issue Jan 13, 2022 · 0 comments
Assignees

Comments

@xiaoiver
Copy link
Contributor

xiaoiver commented Jan 13, 2022

长久以来 MacOS 和 iOS 都不支持 WebGL2,例如目前在我的电脑上:Safari 版本14.1.2 (16611.3.10.1.6)
不过 Safari 15 终于支持了,这让 WebGL2 的支持度上升了不少:
https://developer.apple.com/documentation/safari-release-notes/safari-15-release-notes#Web-APIs

截屏2022-01-19 上午11 33 29

目前 G 的 HAL 优先使用 WebGL2 的适配。兼容 WebGL1 需要从 Shader 和 API 两方面入手。
https://github.com/shrekshao/MoveWebGL1EngineToWebGL2/blob/master/Move-a-WebGL-1-Engine-To-WebGL-2-Blog-2.md

g-webgl 会根据运行环境,按照 WebGPU -> WebGL2 -> WebGL1 的优先级进行降级。
当然用户也可以手动指定,例如忽略 WebGPU,仅使用 WebGL2 和 WebGL1:

const renderer = new WebGLRenderer({
  targets: ['webgl2', 'webgl1'],
});

Shader 语言

我们希望用一套 Shader 代码支持 WebGL1/2 和 WebGPU。后者通过 WASM 转译,而 GLSL 300 到 100 的兼容进行简单字符串替换即可,可参考 luma.gl:
https://github.com/visgl/luma.gl/blob/master/dev-docs/RFCs/v6.0/portable-glsl-300-rfc.md

UBO

// WebGL2 GLSL 300
layout(std140) uniform ub_SceneParams {
  mat4 u_ProjectionMatrix;
  mat4 u_ViewMatrix;
}

// WebGL1 GLSL 100
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_ViewMatrix;

// WebGPU WGSL
[[block]]
struct ub_SceneParams {
    u_ProjectionMatrix: mat4x4<f32>;
    u_ViewMatrix: mat4x4<f32>;
};

API 适配

由于 WebGL 1/2 还有大量通用的 API,因此我们提供一套 HAL 平台实现,内部判断是否 WebGL2。

扩展

很多 WebGL1 扩展在 WebGL2 中都已经内置:

if (!isWebGL2(gl)) {
  this.OES_vertex_array_object = gl.getExtension('OES_vertex_array_object');
  this.ANGLE_instanced_arrays = gl.getExtension('ANGLE_instanced_arrays');
  this.OES_texture_float = gl.getExtension('OES_texture_float');
  this.WEBGL_draw_buffers = gl.getExtension('WEBGL_draw_buffers');
  this.WEBGL_depth_texture = gl.getExtension('WEBGL_depth_texture');
  // @see https://developer.mozilla.org/en-US/docs/Web/API/EXT_frag_depth
  gl.getExtension('EXT_frag_depth');
  // @see https://developer.mozilla.org/en-US/docs/Web/API/OES_element_index_uint
  gl.getExtension('OES_element_index_uint');

  if (this.WEBGL_draw_buffers) {
    this.supportMRT = true;
  }
} else {
  this.EXT_texture_norm16 = gl.getExtension('EXT_texture_norm16');
  this.supportMRT = true;
}

this.WEBGL_compressed_texture_s3tc = gl.getExtension('WEBGL_compressed_texture_s3tc');
this.WEBGL_compressed_texture_s3tc_srgb = gl.getExtension('WEBGL_compressed_texture_s3tc_srgb');
this.EXT_texture_compression_rgtc = gl.getExtension('EXT_texture_compression_rgtc');
this.EXT_texture_filter_anisotropic = gl.getExtension('EXT_texture_filter_anisotropic');
this.KHR_parallel_shader_compile = gl.getExtension('KHR_parallel_shader_compile');
this.OES_draw_buffers_indexed = gl.getExtension('OES_draw_buffers_indexed');

例如 VAO 的用法,WebGL2 直接使用,WebGL1 通过扩展使用:

if (isWebGL2(gl)) {
  gl.bindVertexArray(this.device.currentBoundVAO);
} else {
  device.OES_vertex_array_object.bindVertexArrayOES(this.device.currentBoundVAO);
}

Polyfill

对于部分 WebGL1 上下文中扩展仍不可用的情况,需要使用 polyfill,例如 VAO:

OESVertexArrayObject.prototype.createVertexArrayOES = function createVertexArrayOES() {
  const arrayObject = new WebGLVertexArrayObjectOES(this);
  this.vertexArrayObjects.push(arrayObject);
  return arrayObject;
};

AttribLocation

不再需要调用 gl.getAttribLocation 获取每个 Attribute 的绑定位置了,可以在 Shader 中使用 layout 修饰符手动指定:

#version 300 es
#define POSITION_LOCATION 0
#define TEXCOORD_LOCATION 4
// ...
layout(location = POSITION_LOCATION) in vec2 position;
layout(location = TEXCOORD_LOCATION) in vec2 texcoord;

要注意浏览器实现时 attrib 最大数量限制:

gl.getParameter(MAX_VERTEX_ATTRIBS)

Uniform

这部分用法差异很大,WebGL2 中我们可以使用 UBO,但 WebGL1 中没有,需要配合 Shader 代码解析按名称访问。

例如我们可以一次性设置 UBO,对所有 Program 可见。在设置时不需要考虑 Shader 中的 uniform 名称,只需要考虑内存排布(std 140):

// WebGL2
let offs = template.allocateUniformBuffer(0, 16 + 16);
let d = template.mapUniformBufferF32(0);
offs += fillMatrix4x4(d, offs, this.camera.getPerspective()); // u_ProjectionMatrix 16
offs += fillMatrix4x4(d, offs, this.camera.getViewTransform()); // u_ViewMatrix 16

但在 WebGL 1 中,我们不得不重复为每个 Program 设置,即使这些场景级别的变量都是一样的。

Blit

在 WebGL2 中可以通过 blit 在 framebuffer 间同步,例如输出到屏幕:

if (isWebGL2(gl)) {
  // @see https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/blitFramebuffer
  gl.blitFramebuffer()
} else {
  // 使用 CopyProgram
}

截屏2022-01-19 下午12 55 01

但 WebGL1 需要一个额外的 copy program,下图展示了绘制这个“全屏三角”的过程:

截屏2022-01-19 上午11 16 52

Sampler

WebGL1 中采样器信息是和纹理一同存储的,但 WebGL2 支持多个纹理间共享同一个采样器。

if (isWebGL2(gl)) {
  const gl_sampler = this.device.ensureResourceExists(gl.createSampler());
  gl.samplerParameteri(gl_sampler, GL.TEXTURE_WRAP_S, translateWrapMode(descriptor.wrapS));
  this.gl_sampler = gl_sampler;
} else {
  // use later in WebGL1
  this.descriptor = descriptor;
}

WebGL1 中绑定纹理时需要同时设置采样器参数:

截屏2022-01-19 下午2 17 22

Mipmap

WebGL1 对于 NPOT 纹理是无法生成 mipmap 的,WebGL2 则没有这个限制。

Immutable Texture

https://github.com/WebGLSamples/WebGL2Samples/blob/master/samples/texture_immutable.html

gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB8, 512, 512);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGB, gl.UNSIGNED_BYTE, image);

Readback

WebGL2 中读取纹理 / Buffer 中数据需要配合 fence 完成异步操作:

private async getBufferSubDataAsync(
  target: number,
  buffer: WebGLBuffer,
  srcByteOffset: number,
  dstBuffer: ArrayBufferView,
  dstOffset = 0,
  length = dstBuffer.byteLength || 0,
) {
  const gl = this.device.gl;
  if (isWebGL2(gl)) {
    // @see https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/fenceSync
    this.gl_sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
    gl.flush();

    await this.clientWaitAsync(this.gl_sync, 0, 10);

    gl.bindBuffer(target, buffer);
    gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length);
    gl.bindBuffer(target, null);

    return dstBuffer;
  }
}

WebGL1 只能读纹理数据,无法读取 Buffer:

gl.pixelStorei(gl.PACK_ALIGNMENT, 4);
gl.readPixels(x, y, width, height, gl.RGBA, gl_type, dstBuffer);

其他无法兼容的特性

例如 Query 这样的特性 WebGL1 是没有的。

浏览器实现差异

即使都是 WebGL1,浏览器在实现时也有差异,例如 Safari 中不支持部分 GLSL 内置函数例如 transpose inverse

@xiaoiver xiaoiver self-assigned this Jan 13, 2022
@xiaoiver xiaoiver changed the title HAL 适配 WebGL1 [g-webgl] HAL 适配 WebGL1 Jan 18, 2022
xiaoiver added a commit that referenced this issue Jan 24, 2022
xiaoiver added a commit that referenced this issue Jan 24, 2022
* fix: control render order with renderInst's sortKey #859

* fix: end point in polygon & path #860

* fix: support webgl1 in hal #851

* feat: add targets config in g-webgl

Co-authored-by: yuqi.pyq <[email protected]>
xiaoiver added a commit that referenced this issue Feb 5, 2022
* fix: control render order with renderInst's sortKey #859

* fix: end point in polygon & path #860

* fix: support webgl1 in hal #851

* feat: add targets config in g-webgl

* build: merge build commands

* Publish

 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]
 - @antv/[email protected]

* chore: update changelog

Co-authored-by: yuqi.pyq <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant