w3c的doc国内可能不容易打开,可以参看Orillusion的官方翻译
https://www.orillusion.com/zh/wgsl.html#var-and-let
shuangliu 发布的最佳帖子
-
RE: WGSL | 模块变量和常量的源码案例
-
RE: 顶点格式下的offset: 怎么理解?
offset
用来标识attributes
的偏移量,你的另一个帖子中
https://forum.orillusion.com/topic/28/关于setvertexbuffer-插槽的理解
用setVertexBuffer(index, vertexBufer)
根据不同的index
可以在vertex shader 中设置不同的 location 插槽,这是一种做法,调用多次setVertexBuffer
设置不同插槽除了设置不同的index,我们还可以通过一次
setVertexBuffer
直接把所有顶点相关信息一次性提交,效率更高,比如一般常规来说,我们的geometry信息会包含 position、normal或color、uv三种信息,那么可以直接设置一个大的buffer:const cubeVertexArray = new Float32Array([ // float4 position, float4 color, float2 uv, 1, -1, 1, 1, 1, 0, 1, 1, 1, 1, -1, -1, 1, 1, 0, 0, 1, 1, 0, 1, -1, -1, -1, 1, 0, 0, 0, 1, 0, 0, ...... // 省略篇幅 1, 1, -1, 1, 1, 1, 0, 1, 1, 0, 1, -1, -1, 1, 1, 0, 0, 1, 1, 1, -1, 1, -1, 1, 0, 1, 0, 1, 0, 0, ]);
那么在pipeline创建时可以通过
offset
指定buffer的偏移量用来标识不同的插槽属性:const renderPipeline = this.device.createRenderPipeline({ vertex: { module: vertexShader, entryPoint: 'main', buffers: [{ arrayStride: 10 * 4, // 标明每 10 个值一组数据 attributes: [ { // position shaderLocation: 0, // 插槽index offset: 0, // 偏移量 0 format: 'float32x4', // 标明4个 float32 position }, { // color shaderLocation: 1, // 插槽index offset: 4 * 4, // 偏移量 4 format: 'float32x4', // 标明 4 个 float32 color }, { // uv shaderLocation: 2, // 插槽index offset: 8 * 4, // 偏移量 8 format: 'float32x2', // 标明 2 个 float32 uv } ], }] }, .... // 省略 })
那么只需设置一次vertexbuffer
const verticesBuffer = device.createBuffer({ size: cubeVertexArray.byteLength, usage: GPUBufferUsage.VERTEX, mappedAtCreation: true, }); new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray); verticesBuffer.unmap(); ... // 省略 passEncoder.setVertexBuffer(0, verticesBuffer);
在shader中,就可以通过offset调用不同的location:
[[stage(vertex)]] fn main([[location(0)]] position : vec4<f32>, [[location(1)]] color : vec4<f32>, [[location(2)]] uv : vec2<f32>, ) -> VertexOutput { ... // 省略 }
这种做法效率更高一些,做高性能的 webgpu 程序要尽可能减少不必要的command次数 和 gpu 数据交换次数,所以:
- 减少
setVertexBuffer
的次数,把所有相关信息一次提交,要比多次设置index快很多 - 在数据层面,创建一个gpubuffer,要比创建多个零散的gpubuffer效率高很多,减少cpu与gpu的交换数据次数,也减少了GPU在内部命中内存的速度,这个的提升也很重要。
- 减少
-
RE: 通过device.createTexture()创建纹理时的一些参数问题
-
用来标明是拷贝的来源还是目标,
copy_dst
就是可以做为 copy 的目标,比如用copyExternalImageToTexture
把 image
拷贝给 texture。相应的,copy_src
就是可以作为 copy 来源,也就是可以被拷贝,比如用copyTexureToTexture
,t1 copy to t2,那t1需要有copy_src
,t2要有copy_dst
-
因为纹理贴图支持多维度的贴图,除了普通的
2d
贴图,webgpu 还支持1d, cube, 3d, 2d-array, 3d-array
等类型,那么就对应的depthOrArrayLayer
标明深度参数或者 Array 的layer数量,比如cube对应的depthOrArrayLayer
就必须是6,其他比如 2d-array 根据贴图数量设置 -
rgba/bgra 只是 rgb 的排列方式不同而已,对应着小端对齐/大端对齐,并没有特殊的不同,我不确定 samplecount 为4,既开启了
MSAA
时,一定要用bgra8unorm
吗?
如果是,可能因为目前webgpu
默认的MSAA
需要硬件/系统驱动支持,所以必须要用 perfered format 才可以。
目前大部分设备的色彩空间格式默认是bgra
排列,起码我手上的 几个 windows, mac 和 ios 都是,所以开启默认的MSAA
时,必须用bgra8unorm
。
至于为什么默认是bgra
,因为bgra
排列更符合cpu/gpu的内存排列方式,读取不需要额外的转换操作,可以直接使用底层驱动API去操作
实践上,一般推荐调用context.getPreferredFormat()
得到系统默认的格式,可以避免一些不必要的问题,理论上性能也更好一些
-
-
Chrome writeBuffer Performance
A simple data performance test between
WebGL.bufferSubData
andWebGPU.writeBuffer
, try to simulate 1million transform matrix submit to GPU:
For my platform:
CPU: Intel i7 8700k
Mem: 16gb
GPU: Intel UHD 630
OS: Mac os 12.01
Chrome: 100.0.4862.3
Firefox: 99.0a1
for > 500k, take 1000k for heavy test
Chrome 100 FireFox 99 WebGL ~13 ms 26-30 ms writeBuffer 50~70 ms 26-30 ms mapAsync/unmap 40-50 ms 70-100 ms It is obviously that
writeBuffer
on Chrome is not ready for big dataset, basically 5-10x times slower thanbufferSubData
. WheremapAsync
slightly faster thanwriteBuffer
But for <150k,
writeBuffer
is fasterChrome 100 FireFox 99 WebGL 2 - 4 ms 5 - 6 ms writeBuffer 1 - 2 ms ~2 ms mapAsync/unmap 5 -10 ms 60 - 100 ms For small dataset, the
mapAsync
is slower …
When it comes to real application, WebGPU could get up to 20x slower then WebGL in Chrome, the device queue will be blocked while writing multi textrure/matrix/index/vertex ..
In our contrast test:
https://contrast.orillusion.comEvent though Orillusion gets much better overall FPS than Three instance draw, Orillusion actually takes 10x longer time than Three.js in sending data to gpu. When it comes to 300k boxes, Orillusion takes over 50-70ms on
writeBuffer
where Three only takes 3-4ms onbufferSubData
.This performance issue has been discussed for a long time, e.g. https://bugs.chromium.org/p/chromium/issues/detail?id=1266727&q=writeBuffer&can=2
But it is still not addressed up to Chrome Canary v101For now, the best practice in Chrome, try
mapAsync
if you are working on very big dataset, but may not get much helpful in real application. UsewriteBuffer
for simple relatively small data is always a good choice. But overall, they are not fast as WebGL so far
By the way, in our real practice,
mapAsync
may not a good choice for heavy rendering interactive Application.
The async callback/promise can be delayed from 4ms up to 20ms, such as mouse/keyboard/ajax/networking events, or othermapAsync
jobs.If you are working on a computing job, the delay is acceptable. But for real-time rendering, it can trigger a wired fps sometime, unstable frame change, hard to control from JS side.
mapAsync
vswriteBuffer
is likerelatively fast but unstable fps
vsrelatively slow but stable fps
.
Welcome anyone try the test, and post your result.
-
RE: Three.js vs Babylon.js vs Orillusion - Box 渲染测试
@ukiasu 现在chrome里还不支持动态选择集成显卡或独立显卡,如果没有手动设置chrome使用3070ti,大概率还是用的i9自带的集成显卡在运行,可以检查一下到底用的哪个gpu
另外,这个webgpu测试中最占据时间的步骤是cpu拷贝数据到gpu,目前的版本这部分的优化还不如webgl的效率,可以去测试一下 webgl和webgpu拷贝数据的效率 https://forum.orillusion.com/topic/39/chrome-writebuffer-performance
如果 Dawn/Chrome 优化的好,理论上应该帧率能提升1倍以上另一点,目前webgpu还不支持硬件级别的光追管线,如果拿纯软件模拟,渲染效率会比较低,实际的意义不大,这部分可能要等webgpu下一个标准去支持
-
RE: WebGPU 更新 texture
1.首先并不需要每个texture对应一个pipeline,可以在同一pipeline中使用不同的BindGroup,每个group对应不同的texture binding. e.g.
let sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear', }); let texture1 = device.createTexture({ ... }); let texture2 = device.createTexture({ ... }); const group1 = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 1, resource: sampler, }, { binding: 2, resource: texture1.createView(), } ] }); const group2 = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 1, resource: sampler, }, { binding: 2, resource: texture2.createView(), } ] });
然后在 rendering loop 中通过 setBindGroup 切换 group1 和 group 2 即可。
2.如果一组 textures 是同样的大小和格式,可以直接使用 texture_2d_array 去存储多个 image,这样即使一个group内,也可以在shader中通过切换array的index,来直接切换 texutre.
对于动态的 video texuture, 我们可以直接使用
importExternalTexture
进行引入,随着video播放,texture 会自动更新const video = document.createElement('video'); video.loop = true; video.autoplay = true; video.muted = true; video.src = '...'; await video.play(); const group = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 1, resource: sampler, }, { binding: 2, resource: device.importExternalTexture({ source: video, }) } ] });
3.对于更新静态的 texutre,可以根据目标 image/texture 的类型进行 copy 更新,目前 webgpu 提供多个copy command,我们一般最主要会用到的:
copyExternalImageToTexture
,可以更新外部image到gpu texture:let texture = device.createTexture({ ... }); const img1 = document.createElement('img'); img1.src = '....'; await img1.decode(); const image1 = await createImageBitmap(img1); device.queue.copyExternalImageToTexture( { source: image1 }, { texture: texture }, [image1.width, image1.height] ); ... ...js // 更新texture的image const img2 = document.createElement('img'); img2.src = '....'; await img2.decode(); const image2 = await createImageBitmap(img2); device.queue.copyExternalImageToTexture( { source: image2 }, { texture: texture }, [image2.width, image2.height] );
另外,也可以用 commandEncoder 执行
copyTextureToTexture
进行两个 gpu texutre 之间的copy 更新// e.g. 创建两个texture,texture1 用于显示,tempTexture用于接收外部图片,图片loading 后进行 copy 更新 let texture1 = device.createTexture({ ... }); ... let tempTexture = device.createTexture({ ... }); let newImage = await loadImage() // 异步加载图片 device.queue.copyExternalImageToTexture( { source: newImage }, { texture: tempTexture }, [newImage.width, newImage.height] ); const commandEncoder = device.createCommandEncoder(); commandEncoder.copyTextureToTexture( { texture: tempTexture }, { texture: texture1 } ); device.queue.submit([commandEncoder.finish()]);
其他的更新texture API 使用方法,可以参阅 https://www.orillusion.com/webgpu.html#image-copies
4.对于 sampler,目前 webgpu 没有直接更新 sampler 的API,只能创建新的 sampler,并使用新的group进行渲染,e.g.
let linearSampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear', }); const group = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 1, resource: linearSampler, }, { binding: 2, resource: texture } ] }); let nearestSampler = device.createSampler({ magFilter: 'nearest', minFilter: 'nearest', }); const newGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 1, resource: nearestSampler, }, { binding: 2, resource: texture } ] });
group中其他的resource并不需要重新创建,可以复用
-
RE: WGSL中生成随机数
这个跟 WGSL 无关,任何一种shader 语言都没有自带 Math.random() 这种API,都需要开发者实现基本的伪随机算法,网上公开的算法和例子很多,比如
https://zhuanlan.zhihu.com/p/390862782
https://gamedev.stackexchange.com/questions/32681/random-number-hlsl基本上利用 fract/sin/cos 处理 position/uv/ 就可以实现基本的伪随机
-
RE: 顶点格式下的offset: 怎么理解?
@wenhao0807 这个是根据你自己的vertex设置安排的
const cubeVertexArray = new Float32Array([ // float4 position, float4 color, float2 uv, 1, -1, 1, 1, 1, 0, 1, 1, 1, 1, -1, -1, 1, 1, 0, 0, 1, 1, 0, 1, -1, -1, -1, 1, 0, 0, 0, 1, 0, 0, ...... // 省略篇幅 1, 1, -1, 1, 1, 1, 0, 1, 1, 0, 1, -1, -1, 1, 1, 0, 0, 1, 1, 1, -1, 1, -1, 1, 0, 1, 0, 1, 0, 0, ]);
这个例子中,一行10个数字,你可以安排排列方式,1-4 是position, 5-8 是 color或者一般设置normal,9-10是uv值,相应的就是 pipeline 中的 offset。
一般来说,对于普通object/mesh,我们一般用8个值做顶点buffer,3个 position, 3个normal, 2个uv:buffers: [ { arrayStride: 8 * 4, attributes: [ { // position shaderLocation: 0, offset: 0, format: 'float32x3', }, { // normal shaderLocation: 1, offset: 3 * 4, format: 'float32x3', }, { // uv shaderLocation: 2, offset: 6 * 4, format: 'float32x2', } ], } ]
其他信息,比如transform,color,light,material等参数会用 uniform/storage buffer进行传递。当然你可以自行安排,比如对于lines/points,就不需要normal或者uv,那vertexbuffer中就会设置color
shuangliu 发布的最新帖子
-
RE: Three.js vs Babylon.js vs Orillusion - Box 渲染测试
@ukiasu 现在chrome里还不支持动态选择集成显卡或独立显卡,如果没有手动设置chrome使用3070ti,大概率还是用的i9自带的集成显卡在运行,可以检查一下到底用的哪个gpu
另外,这个webgpu测试中最占据时间的步骤是cpu拷贝数据到gpu,目前的版本这部分的优化还不如webgl的效率,可以去测试一下 webgl和webgpu拷贝数据的效率 https://forum.orillusion.com/topic/39/chrome-writebuffer-performance
如果 Dawn/Chrome 优化的好,理论上应该帧率能提升1倍以上另一点,目前webgpu还不支持硬件级别的光追管线,如果拿纯软件模拟,渲染效率会比较低,实际的意义不大,这部分可能要等webgpu下一个标准去支持
-
RE: google的Dawn挂梯子也Clone不下来
首先,只设置
http.proxy
就行其次,请明确本地socks5的端口,不是所有的梯子都用1080/1086/443,每个软件都不一样,不要只是搜了网上的例子就粘过来用,比如,我用的
clash
一般是使用7890
端口git config --global http.proxy 'socks5://127.0.0.1:7890'
刚测试过,可以 clone Dawn 的 repo
-
RE: google的Dawn挂梯子也Clone不下来
@FuXii 目前一般的梯子,都是本地的socks5代理,是无法直接代理terminal,git,ssh 这些程序的,需要特殊的配置才可以,先自行搜索一下 git 的代理设置
或者使用 Proxifier 类似的软件,直接转发系统底层tcp/udp 的连接到 socks5 代理 -
RE: 使用 copyTextureToTexture可以将多个渲染结果copy到一个纹理上吗?
@zhoubin10 不确定你的代码是否正确,但可以使用
for
循环进行copyTextureToTexture
这种的操作,将多个贴图进行混合,确实可以用作生成mipmap
-
RE: Texture作为attachment 附件的时候mipLevelCount只能为1,不能大于1吗?
@zhoubin10 一般必须小于等于贴图的纹理维度,有固定的算法
mipLevelCount = Math.floor(Math.log2(Math.max(source.width, source.height))) + 1