欢迎来到 WebGPU 的世界 下

简介: 欢迎来到 WebGPU 的世界

无框架手写WebGPU代码



通过框架,我们可以迅速地跟上技术的前沿。但是,框架的封装也容易让我们迷失对于技术本质的把握。

现在我们来看看如何手写WebGPU代码。

1、从Canvas说起

不管是WebGL还是WebGPU,都是对于Canvas的扩展。做为HTML 5的重要新增功能,大家对于2D的Canvas应该都不陌生。

比如我们要画一个三角形,就可以调用lineTo API来实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas</title>
</head>
<body>
    <canvas id="webcanvas" width="200" height="200" style="background-color: #eee"></canvas>
    <script>
        const canvas=document.getElementById('webcanvas');
        const ctx=canvas.getContext('2d');
        ctx.beginPath();
        ctx.moveTo(75,50);
        ctx.lineTo(100,75);
        ctx.lineTo(100,25);
        ctx.fill();
    </script>
</body>

画出来的结果如下:

image.gif图片.png

我们要修改画出来的图的颜色怎么办?

ctx有fillStyle属性,支持CSS的颜色字符串。

比如我们设成红色,可以这么写:

ctx.fillStyle = 'red';

也可以这么写:

ctx.fillStyle = '#F00';

还可以这么写:

ctx.fillStyle = 'rgb(255,0,0,1)';


2、从2D到3D

从2D Canvas到3D WebGL的最大跨越,就是从调用API,到完全不同于JavaScript的新语言GLSL的出场。

第一步的步子我们迈得小一点,不画三角形了,只画一个点。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test OpenGL for a point</title>
</head>
<body>
    <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
    <script>
        const canvas = document.getElementById('webgl');
        const gl = canvas.getContext('webgl');
        const program = gl.createProgram();
        const vertexShaderSource = `
           void main(){
              gl_PointSize=sqrt(20.0);
              gl_Position =vec4(0.0,0.0,0.0,1.0);
           }`;
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vertexShaderSource);
        gl.compileShader(vertexShader);
        gl.attachShader(program, vertexShader);
        const fragShaderSource = `
          void main(){
            gl_FragColor = vec4(1.0,0.0,0.0,1.0);
          }
        `;
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, fragShaderSource);
        gl.compileShader(fragmentShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        gl.useProgram(program);
        gl.drawArrays(gl.POINTS, 0, 1);
    </script>
</body>
</html>

getContext时将2d换成webgl。

我们可以加一行console.log(gl)来看下gl是什么东西:

1.png

我们可以看到,它是一个WebGLRenderingContext对象。

顺便说一句,之前我们拿到的2D的Context是CanvasRenderingContext2D。

下面就引入了两段程序中的程序,第一段叫做顶点着色器,用于顶点的坐标信息。第二段叫做片元着色器,用于配置如何进行一些属性的操作,在本例中我们做一个最基本的操作,改颜色。

我们先看顶点着色器的代码:

           void main(){
              gl_PointSize=sqrt(20.0);
              gl_Position =vec4(0.0,0.0,0.0,1.0);
           }

像其他语言一样,glsl中的代码也需要一个入口函数。

gl_PointSize是一个系统变量,用于存储点的大小。我特意给大小加个了sqrt函数,给大家展示glsl的库函数。

gl_Position用于存储起点的位置。vec4是由4个元素构成的向量。

GLSL的数据类型很丰富,包括标量、向量、数组、矩阵、结构体和采样器等。

标量有布尔型bool, 有符号整数int, 无符号整数uint和浮点数float 4种类型。

类型的使用方式跟C语言一样,比如我们用float来定义浮点变量。

                float pointSize = sqrt(20.0);
                gl_PointSize=pointSize;

GLSL没有double这样表示双精度的类型。在顶点着色器中是没有精度设置的。

但是在片元着色器中有精度的设置,需要指定低精度lowp, 中精度mediump和高精度highp. 一般采用中精度:

            void main(){    
                mediump vec4 pointColor;
                pointColor.r = 1.0;
                pointColor.a = 1.0;
                gl_FragColor = pointColor;
            }

GLSL因为是基于C语言设计的,不支持泛型,所以每种向量同时有4种子类型的。

以四元组vec4为例,有4种类型:

  • vec4: 浮点型向量
  • ivec4: 整数型向量
  • uvec4: 无符号整数向量
  • bvec4: 布尔型向量。

另外还有vec2, vec3各有4种子类型,以此类推。

在GLSL里面,四元向量最常用的用途有两种,在顶点着色器里充当坐标,和在片元着色器里充当颜色。

当vec4作为坐标使用时,我们可以用x,y,z,w属性来对应4个维度。

我们来看个例子:

                vec4 pos;
                pos.x = 0.0;
                pos.y = 0.0;
                pos.z = 0.0;
                pos.w = 1.0;
                gl_Position = pos;

同样,我们在片元着色器里面表示红色的时候只用指令r和a两个属性,g,b让它们默认是0:

            void main(){    
                mediump vec4 pointColor;
                pointColor.r = 1.0;
                pointColor.a = 1.0;
                gl_FragColor = pointColor;
            }

有了顶点着色器和片元着色器的GLSL代码之后,我们将其进行编程,并attach到program上面。

最后再link和use这个program,就可以调用drawArrays来进行绘制了。


3、更现代的GPU编程方法

跨越了从 Canvas API到GLSL的鸿沟了之后,最后到WebGPU这一步相对就容易一些了。

我们要熟悉的是以Vulkan为代表的更现代的GPU的编程方法。

渲染管线不再是唯一,我们可以使用更通用的计算管线了。也不再有顶点着色器和片元着色器那么严格的限制。

另外最重要的一点是,为了提升GPU执行效率,WebGPU不再是像WebGL一样基本每一步都要由CPU来控制,我们使用commandEncoder将所有GPU指令打包在一起,一次性执行。

我们先看一下完整代码有个印象:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test WebGPU</title>
</head>
<body>
    <canvas id="webgpu" width="500" height="500" style="background-color: blue"></canvas>
    <script>
        async function testGPU() {
            const canvas = document.getElementById('webgpu');
            const gpuContext = canvas.getContext('webgpu');
            const adapter = await navigator.gpu.requestAdapter();
            const device = await adapter.requestDevice();
            presentationFormat = gpuContext.getPreferredFormat(adapter);
            gpuContext.configure({
                device,
                format: presentationFormat
            });
            const triangleVertWGSL = `
            @stage(vertex)
            fn main(@builtin(vertex_index) VertexIndex : u32)
             -> @builtin(position) vec4<f32> {
                var pos = array<vec2<f32>, 3>(
                vec2<f32>(0.0, 0.5),
                vec2<f32>(-0.5, -0.5),
                vec2<f32>(0.5, -0.5));
                return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
            }
            `;
            const redFragWGSL = `
            @stage(fragment)
                fn main() -> @location(0) vec4<f32> {
                return vec4<f32>(1.0, 0.0, 0.0, 1.0);
            }
            `
            const commandEncoder = device.createCommandEncoder();
            const textureView = gpuContext.getCurrentTexture().createView();
            const pipeline = device.createRenderPipeline({
                vertex: {
                    module: device.createShaderModule({
                        code: triangleVertWGSL,
                    }),
                    entryPoint: 'main',
                },
                fragment: {
                    module: device.createShaderModule({
                        code: redFragWGSL,
                    }),
                    entryPoint: 'main',
                    targets: [
                        {
                            format: presentationFormat,
                        },
                    ],
                },
                primitive: {
                    topology: 'triangle-list',
                },
            });
            const renderPassDescriptor = {
                colorAttachments: [
                    {
                        view: textureView,
                        loadValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, 
                        storeOp: 'store',
                    },
                ],
            };
            const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
            console.log(passEncoder);
            passEncoder.setPipeline(pipeline);
            passEncoder.draw(3, 1, 0, 0);
            passEncoder.end();
            device.queue.submit([commandEncoder.finish()]);
        }
        testGPU();
    </script>
</body>
</html>

因为浏览器还没有支持,所以我们需要像Chrome Canary这样的支持最新技术的浏览器。而且还要打开支持的开关,比如在Chrome Canary里是enable-unsafe-webgpu.

图片.png

三角形画出来的结果如下:

图片.png

现在的Context从WebGL的WebGLRenderingContext变成了GPUCanvasContext。

WGSL语言的语法更像Rust,vec4这样的容器可以用泛型的写法绑定类型:

            @stage(vertex)
            fn main(@builtin(vertex_index) VertexIndex : u32)
             -> @builtin(position) vec4<f32> {
                var pos = array<vec2<f32>, 3>(
                vec2<f32>(0.0, 0.5),
                vec2<f32>(-0.5, -0.5),
                vec2<f32>(0.5, -0.5));
                return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
            }

对比下Rust的代码看看像不像:

fn fib2(n: i32) -> i64 {
    if n <= 2 {
        return 1i64
    } else {
        return fib2(n - 1) + fib2(n - 2)
    }
}

WGSL是为了规避知识产权问题发明的新语言,本质上它和GLSL,HLSL等语言一样,都可以编译成Vulkan的SPIR-V二进制格式:

图片.png.

Vulkan不限制使用什么样的语言,既可以使用GLSL, HLSL,也可以使用Open CL或者是Open CL的高级封装SYCL。

转换成SPIR-V格式之后,可以转成iOS上的Metal Shading Language,也可以转成Windows Direct 12上用的DXIL。

WebGPU没有这么自由,发明了一门新语言WGSL,不过其思想都是基于SPIR-V的。

在WebGPU和WGSL还未定版,资料还比较缺乏的情况下,我们可以先学习Vulkan相关的知识,然后迁移到WebGPU上来。本质上是同样的东西,只是封装略有不同。

我们之前学习的GLSL的知识同样用得上,而且在这种类Rust风格中可以写得更爽一些。

比如同样是给片元用的颜色值,在保留了vec4可以继续使用r,g,b,a分量的好处之外,因为指定了f32的精度,就不需要mediump了。而且,类型可以自动推断,我们直接给个var就好了:

            @stage(fragment)
                fn main() -> @location(0) vec4<f32> {
                var triColor = vec4<f32>(0.0,0.0,0.0,0.0);
                triColor.r = 1.0;
                triColor.a = 1.0;    
                return triColor;
            }

有了作为功能核心的WGSL,剩下的工作主要就是组装了。

我们把指令打包在 CommandEncoder中,然后通过beginRenderPass来创建一个渲染Pass,再给这个Pass设置一个渲染的流水线,添加相应的draw操作,最后提交到GPU设备的队列中,就大功告成了。


小结


相对于基于OpenGL ES 2.0的WebGL 1.0,WebGPU更接近于Vulkan这样更能发挥GPU能力的新API,可以更有效地发挥出新的GPU的能力。就像渲染上Three.js和Babylon.js给我们展示的那样和计算上Tensorflow.js的飞跃一样。

虽然浏览器还不支持,但是不成熟的主要是封装,底层的Vulkan和Metal技术已经非常成熟,并且广泛被客户端所使用了。

WebGPU这个能力暴露给H5和小程序之后,将给元宇宙等热门应用插上性能倍增的翅膀。结合WebXR等支持率更成问题的新技术一起,成为未来几年前端的主要工具。

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
相关文章
|
数据可视化 图形学 开发者
【Qt 底层机制之图形渲染引擎】深入理解 Qt 的 渲染机制:从基础渲染到高级图形
【Qt 底层机制之图形渲染引擎】深入理解 Qt 的 渲染机制:从基础渲染到高级图形
2546 4
|
关系型数据库 MySQL 数据库
MySQL开启远程访问权限
默认情况下,mysql只允许本地登录,但是多数情况下,我们需要访问服务器上的数据库资源,此时就需要开放MySQL的远程访问权限。 本文主要讲解如何开启MySQL的远程访问权限。
38258 7
MySQL开启远程访问权限
|
9月前
|
运维 安全 物联网
AR眼镜赋能船舶巡检:打造智能化运维新方案
AR眼镜赋能船舶智能巡检,破解传统模式漏检多、环境恶劣、响应慢、培训长等痛点。融合二维码/RFID识别与虚实交互,实现巡检自动化、维修远程协同,单次巡检从4小时缩至1.5小时,故障修复效率提升50%以上,年省成本超80万元/船,漏检率降至10%以下,助力船舶运维高效安全。(236字)
|
2月前
|
人工智能 自然语言处理 测试技术
Vibe Coding实战:冗长提示词不是关键,工程约束才是落地核心
vibe coding不是拼提示词话术,而是以工程规范约束AI:预设基线、结构化拆解需求、分模块开发、强制配套测试、日志驱动修复。8个商业项目验证,标准化五步法可将接口开发从86分钟缩至26分钟,兼顾效率与可维护性。(239字)
497 2
|
XML 安全 前端开发
一行代码搞定禁用 web 开发者工具
在如今的互联网时代,网页源码的保护显得尤为重要,特别是前端代码,几乎就是明文展示,很容易造成源码泄露,黑客和恶意用户往往会利用浏览器的开发者工具来窃取网站的敏感信息。为了有效防止用户打开浏览器的 Web 开发者工具面板,今天推荐一个不错的 npm 库,可以帮助开发者更好地保护自己的网站源码,本文将介绍该库的功能和使用方法。 功能介绍 npm 库名称:disable-devtool,github 路径:/theajack/disable-devtool。从 f12 按钮,右键单击和浏览器菜单都可以禁用 Web 开发工具。 🚀 一行代码搞定禁用 web 开发者工具 该库有以下特性: • 支持可配
1601 22
|
人工智能 开发框架 自然语言处理
Eko:一句话就能快速构建复杂工作流的 AI 代理开发框架!快速实现自动操作电脑和浏览器完成任务
Eko 是 Fellou AI 推出的开源 AI 代理开发框架,支持自然语言驱动,帮助开发者快速构建从简单指令到复杂工作流的智能代理。
2148 12
Eko:一句话就能快速构建复杂工作流的 AI 代理开发框架!快速实现自动操作电脑和浏览器完成任务
|
负载均衡 5G UED
蜂窝网络中的切换(Handover)及其类型详解
蜂窝网络中的切换(Handover)及其类型详解
2451 12
|
安全 jenkins 持续交付
如何在 Jenkins 中配置邮件通知?
如何在 Jenkins 中配置邮件通知?
1083 11
|
小程序 开发者
万能的微信小程序个人主页:商城系统个人主页、外卖系统个人主页、购票系统个人主页等等【全部源代码分享+页面效果展示+直接复制粘贴编译即可】
这篇文章分享了四个不同应用场景下的微信小程序个人主页的源代码和页面效果展示,包括商城系统、外卖系统、医疗挂号和电影购票系统的个人主页。提供了完整的页面布局和样式代码,允许开发者直接复制粘贴并根据自己的项目需求进行简单的改造使用。
万能的微信小程序个人主页:商城系统个人主页、外卖系统个人主页、购票系统个人主页等等【全部源代码分享+页面效果展示+直接复制粘贴编译即可】