Part 2:WebGPU基本对象及其创建

📅 2026/7/16 3:30:00 👁️ 阅读次数 📝 编程学习
Part 2:WebGPU基本对象及其创建

WebGPU基本对象及其创建

摘要:本文详细介绍了 WebGPU 核心对象的创建流程与作用。首先通过浏览器 Navigator.gpu 检测支持性并获取 GPUAdapter,进而创建 GPUDevice 作为与 GPU 的逻辑连接。GPUDevice 用于创建各类资源(缓冲区、纹理、管线等)及 GPUCommandEncoder 来编码指令。指令缓冲区通过 GPUQueue 提交执行。若需图形渲染,还需将 GPUDevice 与 HTML Canvas 元素关联:获取 Canvas 上下文并配置纹理格式。文章最后给出了完整的初始化代码示例,涵盖从检测支持到画布配置的全过程。

  • Part 1:WebGPU的因承和编程范式

📑 目录

  • 总体流程
  • Navigator
  • GPUDevice
  • GPUCommandEncoder
  • GPUQueue
  • Canvas元素
  • Canvas Context
  • 一个完整的初始化流程

总体流程

设备 GPU 和运行 WebGPU API 的 Web 浏览器之间有几个抽象层。在开始学习 WebGPU 时了解这些抽象层很有用

WebGPU操作的核心范式是:将指令和数据打包为指令缓冲区(command buffer)对象,提交到与GPU关联的队列(queue)中执行。完成这一核心流程至少需要以下六个步骤:

  1. 访问浏览器的Navigator对象,检查其是否支持WebGPU;
  2. 若支持,通过Navigator获取GPUAdapter(适配器);
  3. 使用GPUAdapter获取GPUDevice(设备的逻辑连接);
  4. 使用GPUDevice创建GPUCommandEncoder(指令编码器);
  5. 配置该编码器,将具体指令录入到指令缓冲区;
  6. 将指令缓冲区提交到与GPUDevice关联的GPUQueue

如果应用还需要在页面中进行图形渲染(而不仅仅是通用计算),则还需要将GPUDevice与页面中的<canvas>元素关联起来,这需要额外的三个步骤:

  1. 在HTML文档中访问<canvas>元素;
  2. 通过该元素获取GPUCanvasContext(画布上下文);
  3. 使用GPUDevice及所需的纹理格式对GPUCanvasContext进行配置。

完成上述关联后,应用便可以通过GPUDevice发出的渲染指令,将图形结果绘制到画布上。下面依次介绍流程中涉及的各个核心对象。

Navigator

现代浏览器都提供全局的Navigator对象,用于暴露浏览器自身的能力信息。例如clipboard属性可用于读取系统剪贴板,connection属性用于检测网络连接状况,deviceMemory属性用于查看可用内存等。

如果当前浏览器支持WebGPU,Navigator对象上会存在一个gpu属性(类型为GPU)。因此可以用以下代码来检测浏览器是否支持WebGPU:

// 检查浏览器是否支持WebGPUif(!navigator.gpu){thrownewError("浏览器不支持WebGPU。");}

gpu属性提供了两个重要的方法:

方法说明
requestAdapter()返回一个Promise,解析为一个GPUAdapter对象(若失败则解析为null
getPreferredCanvasFormat()返回一个字符串,表示当前浏览器/设备下最适合用于画布渲染的纹理格式

需要特别注意:浏览器支持WebGPU这一API,并不代表客户端硬件一定支持WebGPU所需的渲染与计算能力。必须实际调用requestAdapter()来进行确认,如:

// 访问GPU适配器constadapter=awaitnavigator.gpu.requestAdapter();if(!adapter){thrownewError("没有找到合适的GPUAdapter。");}

GPUAdapter代表了对客户端某个物理(或软件模拟的)GPU实现的一次抽象访问,它本身并不直接执行渲染或计算,而是用来查询设备能力,并进一步获取GPUDevice

关于适配器信息:在早期版本的规范中,需要通过异步方法adapter.requestAdapterInfo()获取设备信息;在当前的WebGPU标准中,这一信息已改为通过只读属性adapter.info(类型为GPUAdapterInfo)同步获取,不再需要awaitGPUAdapterInfo包含以下只读字段:

属性类型描述
vendorStringGPU的制造商标识,如nvidiaamdintel
deviceString设备名称标识
architectureString设备架构标识
descriptionString适配器的描述信息

这些信息在应用需要针对不同厂商GPU执行不同代码路径(例如规避某些驱动缺陷)时非常有用。

GPUAdapter还有两个额外的属性:

  • features是一个GPUSupportedFeatures对象(类似Set的结构),列出了该GPU所支持的可选特性
  • limits是一个GPUSupportedLimits对象,描述了GPU在各项操作参数上的最大/最小取值范围

可以使用adapter.features.has(featureName)判断设备是否支持某项特性,也可以直接遍历features集合获取全部已支持特性。下表列出了WebGPU中常见的可选特性:

特性ID描述
depth-clip-control允许应用使用类似OpenGL的深度范围[-1, 1],而非WebGPU默认的[0, 1]
depth32float-stencil8深度/模板缓冲区中的元素可以是32位浮点深度值加8位模板值
texture-compression-bc支持使用块压缩(BC)算法压缩的纹理
texture-compression-etc2支持使用Ericsson纹理压缩2(ETC2)算法压缩的纹理
texture-compression-astc支持使用自适应可伸缩纹理压缩(ASTC)算法压缩的纹理
indirect-first-instance允许间接绘制调用(indirect draw)中的firstInstance参数设置为大于0的值
shader-f16着色器中可以访问16位半精度浮点数(f16
rg11b10ufloat-renderable允许将rg11b10ufloat用作可渲染的纹理格式
bgra8unorm-storage允许创建像素格式为bgra8unorm的存储纹理(storage texture)
float32-filterableGPU可以对32位浮点纹理进行线性过滤(平滑处理)

更多可用功能见MDN GPUSupportedFeatures文档。当然,GPUAdapter最重要的作用还是提供访问GPUDevice

GPUDevice

可以通过以下代码获取GPUDevice

// 访问GPUconstdevice=awaitadapter.requestDevice();if(!device){thrownewError("创建GPUDevice失败。");}

GPUDevice代表了应用与客户端GPU之间的一条逻辑连接,在WebGPU开发中处于绝对核心的位置。几乎所有WebGPU资源与操作对象都是通过它的方法创建的,包括但不限于:

  • createBuffer():创建缓冲区(buffer)资源;
  • createTexture()/createSampler():创建纹理与采样器;
  • createShaderModule():创建着色器模块;
  • createBindGroup()/createBindGroupLayout():创建资源绑定组及其布局;
  • createRenderPipeline()/createComputePipeline():创建渲染管线与计算管线;
  • createCommandEncoder():创建指令编码器。

此外,GPUDevice还继承自EventTarget,因此可以通过device.addEventListener("uncapturederror", ...)监听未被捕获的错误;同时它还提供了一个device.lostPromise),可用于监控设备连接意外丢失的情况。这些细节超出了本章范围,后续章节会陆续展开。

GPUCommandEncoder

GPUCommandEncoder负责创建并临时存储将要发送给GPU的一系列指令,它通过调用GPUDevice上的方法创建:

// 创建command encoderencoder=device.createCommandEncoder();if(!encoder){thrownewError("创建GPUCommandEncoder失败。");}

有了这个对象,就可以定义要发送给设备执行的具体操作了。它的方法可以分为两大类:

(1)开启子编码器 / 完成编码(最常用的三个方法):

方法描述
beginRenderPass()开始编码一个渲染通道,返回GPURenderPassEncoder,用于定义图形渲染相关操作
beginComputePass()开始编码一个计算通道,返回GPUComputePassEncoder,用于定义通用计算相关操作
finish()结束该编码器全部指令的录入,返回一个GPUCommandBuffer,随后即可提交给设备队列执行

(2)资源操作与调试辅助方法(除以上三个核心方法外,GPUCommandEncoder还提供了下列方法,用于在缓冲区/纹理之间拷贝数据,以及插入调试信息):

方法描述
clearBuffer()将缓冲区内容清零
copyBufferToBuffer()在两个缓冲区之间拷贝数据
copyBufferToTexture()将缓冲区数据拷贝到纹理
copyTextureToBuffer()将纹理数据拷贝到缓冲区
copyTextureToTexture()在两个纹理之间拷贝数据
pushDebugGroup()/popDebugGroup()开启/结束一个带标签的调试分组,便于在调试工具中定位指令
insertDebugMarker()在指令序列中插入一条调试标记
resolveQuerySet()将查询集合(如时间戳查询、遮挡查询)的结果写入缓冲区
writeTimestamp()向查询集合写入一个时间戳(用于性能分析)

其中,finish()是每个非平凡WebGPU应用都必须调用的方法:它将编码器已录入的全部数据打包为一个GPUCommandBuffer。一旦这个缓冲区被提交到设备队列,GPU便会按顺序开始执行其中的指令。

GPUQueue

GPUDevice还有一个重要的只读属性queue,用于访问与该设备关联的GPUQueue。应用需要显式调用队列上的方法,才能真正触发GPU执行操作。其常用方法包括:

  • writeBuffer(buffer, bufferOffset, data, ...):直接向某个GPU缓冲区写入(更新)数据;
  • writeTexture(destination, data, dataLayout, size):直接向某个GPU纹理写入(更新)数据;
  • submit(commandBuffers):将一个或多个指令缓冲区提交给GPU执行。

调用submit()是整个流程中最关键的一步——GPU会依据其中的指令,真正执行图形渲染或通用计算任务。提交之后,应用还可以调用queue.onSubmittedWorkDone()(返回一个Promise),用来在队列中已提交的全部工作执行完毕后得到通知,从而监控执行进度。

Canvas元素

<canvas>是用于承载图形显示的HTML元素。当WebGPU执行渲染操作时,最终生成的图形会显示在与之关联的画布中。因此,若要用WebGPU进行图形渲染,通常需要以下三个步骤:

  1. 在HTML文档中定义一个<canvas>元素;
  2. 在JavaScript中获取该元素对应的HTMLCanvasElement对象;
  3. 从该元素获取一个渲染上下文,并对其进行配置。

默认情况下,<canvas>元素显示为一块空白矩形。要设置其显示内容,需要先在JavaScript中获取该元素——既可以通过标签选择(document.querySelector("canvas")),也可以通过ID选择(document.getElementById(id))——随后调用该元素上的getContext()方法,获得一个用于绘制内容的上下文对象。

Canvas Context

需要注意,WebGPU只是设置<canvas>显示内容的众多方式之一,此外还可以使用WebGL、2D位图绘制API或简单的2D绘图上下文("2d")。因此需要在调用getContext()时明确指定所需的上下文类型。对于WebGPU应用,应传入字符串"webgpu"

constcanvas=document.getElementById("canvas_example");constcontext=canvas.getContext("webgpu");

由此得到的context对象类型为GPUCanvasContext,它提供了一个关键方法——configure()——用于设置渲染上下文的具体行为。传给configure()的配置对象(GPUCanvasConfiguration)主要包含以下字段:

属性是否必需描述
device用于控制该画布渲染的GPUDevice
format从画布中获取的纹理所使用的像素格式(决定了WebGPU应以何种像素格式向画布输出内容)
usage描述从画布获取的纹理将如何被使用(如GPUTextureUsage.RENDER_ATTACHMENT),默认已包含渲染附件用途
alphaMode控制透明度/合成方式("opaque""premultiplied"
colorSpace获取纹理的色彩空间
viewFormats允许创建的附加视图格式数组

其中最核心的是deviceformat这两个属性:device应设置为前面获取的GPUDeviceformat则决定了WebGPU向画布输出内容时采用的像素格式。由于不同硬件/操作系统所支持的最佳纹理格式并不完全相同,通常不需要手动指定,而是通过调用navigator.gpu.getPreferredCanvasFormat()获取当前系统的最优格式(返回值为"rgba8unorm""bgra8unorm"之一),再将其赋给format

// 获取canvas对象constcanvas=document.getElementById("mycanvas");// 获取该canvas的WebGPU渲染上下文constcontext=canvas.getContext("webgpu");// 获取当前系统下最优的纹理像素格式constcanvasFormat=navigator.gpu.getPreferredCanvasFormat();// 使用device与format配置渲染上下文context.configure({device:device,format:canvasFormat,});

配置完成后,此后由GPUDevice发起的渲染操作产生的图形,便会显示在这个已关联的画布上。

一个完整的初始化流程

综合以上内容,下面给出一个完整、可直接运行的WebGPU初始化流程示例(仅完成对象创建与画布关联,尚未涉及具体的渲染或计算指令):

asyncfunctioninitWebGPU(){// 检查是否支持WebGPUif(!navigator.gpu){thrownewError("浏览器不支持WebGPU。");}// 获取GPUAdapterconstadapter=awaitnavigator.gpu.requestAdapter();if(!adapter){thrownewError("没有找到GPUAdapter");}// 获取GPU设备constdevice=awaitadapter.requestDevice();if(!device){thrownewError("GPUDevice创建失败");}// 创建指令编码器constencoder=device.createCommandEncoder();if(!encoder){thrownewError("GPUCommandEncoder创建失败");}// 获取canvas对象constcanvas=document.getElementById("canvas_example");if(!canvas){thrownewError("无法获取页面的canvas对象");}// 获取canvas的WebGPU上下文constcontext=canvas.getContext("webgpu");// 获取最佳的像素格式constcanvasFormat=navigator.gpu.getPreferredCanvasFormat();// 配置上下文context.configure({device:device,format:canvasFormat,});}initWebGPU();

这就是我们在系列文章的第一篇(WebGPU的因承和编程范式)中定义的初始化函数。至此,我们已经完成了WebGPU应用最基础的初始化环节:确认浏览器与硬件支持能力、建立与GPU的逻辑连接、创建指令编码器,并将渲染目标(canvas)与设备关联起来。在下一篇Part3: 图形渲染中,我们会介绍如何在GPU显存中定义数据,以及如何定义与突然渲染有关的渲染通道。