WebGPU学习(10)---如何利用 WebGPU 实现高性能

news2025/1/15 6:25:58

虽然是WebGPU,但是速度很慢!?

我们将解释如何充分利用 WebGPU 性能。这次我们以绘制大量物体为例,根据“使用纹理”中的代码进行一些更改并绘制 900 个立方体。

要均匀分布立方体,可以按如下方式更新 worldMatrix:

    for (let i=0; i<30*30; i++) {
        draw({context, pipeline, verticesBuffer, indicesBuffer, uniformBindGroup, uniformBuffer, depthTexture, i});
    }
  const worldMatrix = glMatrix.mat4.create();
	const now = Date.now() / 1000;
  glMatrix.mat4.translate(
    worldMatrix,
    worldMatrix,
    glMatrix.vec3.fromValues((i % 30) * 5 - 100, Math.floor(i / 30) * 5 + -50, 0)
  );
  glMatrix.mat4.rotate(
    worldMatrix,
    worldMatrix,
    1,
    glMatrix.vec3.fromValues(Math.sin(now), Math.cos(now), 0)
  );

	g_device.queue.writeBuffer(
    uniformBuffer,
    4 * 16 * 2,
    worldMatrix.buffer,
    worldMatrix.byteOffset,
    worldMatrix.byteLength
  );

可以看一个不考虑性能调整的多路立方体绘制示例。我们发现绘制非常断断续续且缓慢。

缓慢的原因

无法重用CommandEncoder

基本上,g_device.queue.submit([commandEncoder.finish()])速度非常慢。在此代码中,它被调用了 900 次。但是理想情况下,最好只在绘制结束时执行一次。

无法重用RenderPassEncoder

在当前代码中,RenderPassEncoder也无法重用。我们要尽可能的去重复使用RenderPassEncoder,可以从 CommandEncoder 多次生成 RenderPassEncoder。

其他问题

下面的示例将 passEncoder.end();g_device.queue.submit([commandEncoder.finish()]); 放在draw函数之外,以便仅在绘图帧的开头生成 commandEncoder 和 renderPassEncoder。这是示例。

但是我们发现,除了一个立方体之外,所有立方体都消失了。这是因为 GPU 仅在执行 g_device.queue.submit([commandEncoder.finish()]); 时执行绘图命令。

即使每次在绘制函数中更新WorldMatrix,Uniform区域也只是针对一个立方体。当draw函数处理完成并且Uniform区域中的WorldMatrix更新为最后的位置信息后,在绘制帧结束时,所有的立方体最终都通过g_device.queue.submit([commandEncoder.finish()]);来绘制。因此,所有立方体都引用表示最后位置的WorldMatrix,并且所有立方体都绘制在最后位置。

因此,为了将所有立方体绘制在正确的位置,我们需要重写Uniform区域缓冲区,然后每次执行g_device.queue.submit([commandEncoder.finish()]);。然而,这并不能加快 WebGPU 处理速度。

这就是WebGPU编程的难点。我们应该怎么办?

解决方法

一种解决方案是将所有立方体的所有 WorldMatrix 解压到缓冲区中。 然后,仅在绘图帧结束时执行一次 g_device.queue.submit([commandEncoder.finish()]);

这是一个改进版本的示例代码。

const cubeNumber = 30*30;
const vertWGSL = `
struct Uniforms {
  projectionMatrix : mat4x4<f32>,
  viewMatrix : mat4x4<f32>,
}
@binding(0) @group(0) var<uniform> uniforms : Uniforms;

struct WorldStorage {
  worldMatrices : array<mat4x4<f32>>,
}
@binding(3) @group(0) var<storage> worldStorage : WorldStorage;
...

worldMatrix 定义已移至单独的新Storage Buffer。在处理大量数据时,Storage Buffer比Uniform Buffer更好。

900 个 WorldMatrix 以数组格式定义。

@vertex
fn main(
  @builtin(instance_index) instance_index: u32,
  @location(0) position: vec4<f32>,
  @location(1) color: vec4<f32>,
  @location(2) uv: vec2<f32>  
) -> VertexOutput {

	var output : VertexOutput;
	output.Position = uniforms.projectionMatrix * uniforms.viewMatrix * worldUniforms.worldMatrices[instance_index] * position;
  output.fragUV = uv;
  
  return output;
}

WorldMatrix是使用内置变量实例号instance_index从数组中提取的。

  const storageBufferSize = 4 * 16 * cubeNumber; // 4x4 matrix * 3
  const storageBufferCubes = g_device.createBuffer({
    size: storageBufferSize,
    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
  });

这次,我们为多维数据集的数量创建一个新的存储缓冲区“storageBufferCubes”。

  const uniformBindGroup = g_device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),
    entries: [
      {
        binding: 0,
        resource: {
          buffer: uniformBuffer,
        },
      },
      {
        binding: 1,
        resource: texture.createView(),
      },
      {
        binding: 2,
        resource: sampler,
      },
      {
        binding: 3,
        resource: {
        	buffer: storageBufferCubes, // <--- 追加
        }
      },
    ],
  });

BindGroup 还将 storageBufferCubes 指定为binding:3

  for (let i=0; i<cubeNumber; i++) {
  	const worldMatrix = glMatrix.mat4.create();
    const now = Date.now() / 1000;
    glMatrix.mat4.translate(
      worldMatrix,
      worldMatrix,
      glMatrix.vec3.fromValues((i % 30) * 5 - 100, Math.floor(i / 30) * 5 + -50, 0)
    );
    glMatrix.mat4.rotate(
      worldMatrix,
      worldMatrix,
      1,
      glMatrix.vec3.fromValues(Math.sin(now), Math.cos(now), 0)
    );

    g_device.queue.writeBuffer(
      storageBufferCubes,
      4 * 16 * i,
      worldMatrix.buffer,
      worldMatrix.byteOffset,
      worldMatrix.byteLength
    );
  }

在getTransformationMatrix中,900个WorldMatrix被写入storageBufferCubes。

  passEncoder.setPipeline(pipeline);
  passEncoder.setBindGroup(0, uniformBindGroup);
  passEncoder.setVertexBuffer(0, verticesBuffer);
  passEncoder.draw(cubeVertexCount, cubeNumber); // <---绘制900个实例

另外,绘制时,在draw函数的第二个参数中指定要绘制实例的立方体数量。 现在,将一次绘制900个立方体,着色器将根据每个实例编号引用WorldMatrix并在适当的位置绘制。

function frame(
{context, pipeline, verticesBuffer, indicesBuffer, uniformBindGroup, uniformBuffer, depthTexture}:
{context: GPUCanvasContext, pipeline: GPURenderPipeline, verticesBuffer: GPUBuffer, uniformBindGroup: GPUBindGroup, uniformBuffer: GPUBuffer, depthTexture: GPUTexture, texture: GPUTexture}
): void {
  for (let i=0; i<30*30; i++) {
    draw({context, pipeline, verticesBuffer, indicesBuffer, uniformBindGroup, uniformBuffer, depthTexture, i});
  }
  
  passEncoder.end();
  passEncoder = undefined;
  g_device.queue.submit([commandEncoder.finish()]);
  commandEncoder = undefined;
  
  requestAnimationFrame(frame.bind(frame, {context, pipeline, verticesBuffer, uniformBindGroup, uniformBuffer, depthTexture, texture}));
}

请注意, g_device.queue.submit([commandEncoder.finish()]); 仅在绘制帧结束时执行一次。

其他调整

重用RenderPipeline和BindGroup

在这种情况下,我们只需要一个RenderPipeline,但在复杂的场景中,根据对象的不同,使用的着色器和顶点信息会有所不同,因此我们需要相应地使用多个RenderPipeline。 为了加快速度,不要在每次执行绘制过程时生成 RenderPipeline,而是多次重复使用创建的 RenderPipeline。 BindGroup 也是如此。

使用RenderBundle

如果我们是多次绘制常规内容,请考虑将它们转换为 RenderBundle 以重用绘图。 RenderBundle 在“使用 RenderBundle”部分中进行了解释。

总结

使用WebGPU,我们需要自己优化绘图命令,类似于驱动层对WebGL所做的事情。因此,如果编码没有适当优化,结果可能会比WebGL慢。

确定每个 WebGPU 函数调用的性能特征并优化渲染代码非常重要。为了做到这一点,在某些情况下可能需要检查我们正在创建的库或应用程序的设计。在许多情况下,需要将着色器可以访问的大部分数据预先部署到 GPU 上的缓冲区中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1016647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序 解决 当套在scroll-view中后 wx.pageScrollTo 函数失效问题解决

pageScrollTo 只是 页面的API 他对 scroll-view 的滚动是无法控制的 但是 scroll-view 也提供了一个scroll-into-view属性 我们编写一个小案例 wxml 参考代码如下 <view><scroll-view scroll-y"{{ true }}" style"height: 100vh;" scroll-into-v…

笑笑云航服悦《乡村振兴战略下传统村落文化旅游设计》许少辉博士新著

笑笑云航服悦《乡村振兴战略下传统村落文化旅游设计》许少辉博士新著

Android高级开发-APK极致优化

九道工序 1. SVG(Scalable Vector Graphics)可缩放矢量图 使用矢量图代替位图可以减小 APK 的尺寸&#xff0c;因为可以针对不同屏幕密度调整同一文件的大小&#xff0c;而不会降低图像质量。 矢量图首次加载时可能消耗更多的 CPU 资源。之后&#xff0c;二者的内存使用率和…

Spring 篇

1、什么是 Spring&#xff1f; Spring是一个轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架&#xff0c;目的是用于简化企业应用程序的开发&#xff0c;它使得开发者只需要关心业务需求。常见的配置方式有三种&#xff1a;基于XML的配置、基于注解的配置…

06JVM_类加载器

一、类加载器 以JDK8为例&#xff1a; ①启动类加载器 ②扩展类加载器 ③应用程序类加载器 ④自定义类加载器 ①类加载器具有层级关系&#xff0c;当加载一个类的时候&#xff0c;要看所有的上级有没有加载此类。【双亲委派模式】 ②类加载器负责在运行时将Java类动态加载…

4.开放-封闭原则

这个原则其实是有两个特征&#xff0c;一个是说‘对于扩展是开放的(Open for extension)&#xff0c;另一个是说‘对于更改是封闭的(Closed for modification)[ASD]。

走近Callable

1.特点 可以有返回值可以抛出异常方法不同&#xff0c; run() / call(); Callable 接口类似于Runnable &#xff0c;因为它们都是为其实例可能有另一个线程执行的类设计的&#xff0c; 然而&#xff0c;Runnable不返回结果&#xff0c;也不能抛出被检查的异常。 2.代码测试…

互联网数字化管理升级,制造企业一站式智能管理,可定制-亿发

在互联网时代&#xff0c;传统机械制造企业面临着未有的挑战和机遇。信息化管理水平成为企业竞争力的关键因素。然而&#xff0c;许多制造企业在信息化管理中常常陷入以下三大问题&#xff1a; 1、盲目随潮流&#xff0c;缺乏总体规划 互联网时代&#xff0c;科技发展日新月异…

python基础复习-基本数据类型

目录 数字进制转换小数精度科学计算库 字符串转义符正向/反向索引正向/反向切片成员运算字符编码字符串处理 布尔类型指示条件作为掩码 类型转换 数字 进制转换 a10 bbin(a) coct(a) dhex(a) print(a,b,c,d) print(type(a)) print(type(b)) print(type(c)) print(type(d))10 …

Spring Cloud超越微服务:服务网格的崭露头角

文章目录 1. 微服务的挑战2. 什么是服务网格&#xff1f;3. Spring Cloud和服务网格服务发现负载均衡安全性服务网格扩展 4. 服务网格的优势4.1. 解耦通信逻辑4.2. 提高可观察性4.3. 灰度发布和流量控制4.4. 安全性 5. 未来展望6. 结论 &#x1f389;欢迎来到架构设计专栏~Spri…

【办公自动化】用Python在Excel中查找并替换数据(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Python 潮流周刊#20:三种基准测试的方法、为什么代码在函数中运行得更快?

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 你好&#xff0c;我是猫哥。这里每周分享优质的 Python、AI 及通用技术内容&#xff0c;大部分为英文。标题取自其中两则分享&#xff0c;不代表全部内容都是该主题&#xff0c;特此声明。 本周刊由 Python猫 出品…

【Qt】Qt中关联容器QMap,QMultiMap,QHash,QMultiHash 的理解

在Qt中&#xff0c;有几种关联容器可供选择&#xff1a; QMap&#xff1a;QMap是一个关联容器&#xff0c;存储键-值对&#xff0c;并根据键自动进行排序。它提供了快速的查找和插入操作&#xff0c;适用于需要根据键进行排序和搜索的场景。 QMultiMap&#xff1a;QMultiMap是…

软件测评中:电子政务系统怎么测评?

1、文件依据&#xff1a; 1)《中华人民共和国政府采购法实施条例》&#xff08;中华人民共和国国务院令 第658号&#xff09; 第四十一条“大型或者复杂的政府采购项目&#xff0c;应当邀请国家认可的质量检测机构参加验收工作。” 2) 《国务院办公厅关于印发国家政务信息化…

深入Android系统基础知识及基本概念

深入Android系统基础知识及基本概念 Android应用程序的基本组成部分&#xff0c;包括Activities&#xff08;活动&#xff09;、Services&#xff08;服务&#xff09;、Broadcast receivers&#xff08;广播接收器&#xff09;和Content providers&#xff08;内容提供者&…

Mysql---第八篇

系列文章目录 文章目录 系列文章目录一、mysql执行计划怎么看一、mysql执行计划怎么看 执行计划就是sql的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数 EXPLAIN SELECT * from A where X=? and Y=? 1。id :是一个有顺序的编号,是查询的顺序号,有几个 sel…

ModbusTCP服务端

1在Device下&#xff0c;添加设备net&#xff1a; 公交车。 2在net下添加 ModbusTCP

技术人员如何提升商业敏感度?

在商业领域&#xff0c;最基本也是最实用的财务知识&#xff0c;就是看懂三张报表。简单地说&#xff0c;现金流量表&#xff0c;决定企业能不能活下来&#xff1b;资产负债表和利润表&#xff0c;决定企业活得好不好。下面分别来学习这三张报表。 资产负债表 资产负债表&#…

浅谈一下前端字符编码

背景 众所周知&#xff0c;计算机只能识别二进制&#xff0c;它是由逻辑电路组成&#xff0c;逻辑电路通常只有两个状态&#xff0c;开关的接通与断开&#xff0c;这两种状态正好可以用二进制数的0和1表示。但是现实中存在着其他的字符&#xff1a;数字、字母、中文、特殊符号…

如何实现Web应用、网站状态的监控?

如何实现Web应用、网站状态的监控&#xff1f; 关键词&#xff1a;网站监控,服务器监控,页面性能监控,用户体验监控本文通过代码分析、网站应用介绍网站状态监控的方式下文主要分为网站应用、技术实现两部分 一、网站应用 现在网络上已经存在一些Web网站监控的服务&#xff…