你们好,本文学习Chrome->webgpu-samplers->helloTriangle示例。html
上一篇博文:
WebGPU学习(一): 开篇git
下一篇博文:
WebGPU学习(三):MSAAgithub
克隆webgpu-samplers Github Repo到本地。
(备注:当前的version为0.0.2)web
实际的sample代码在src/examples/文件夹中,是typescript代码写的:
typescript
打开helloTriangle.ts文件,咱们来看下init函数的内容。canvas
const vertexShaderGLSL = `#version 450 const vec2 pos[3] = vec2[3](vec2(0.0f, 0.5f), vec2(-0.5f, -0.5f), vec2(0.5f, -0.5f)); void main() { gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); } `; const fragmentShaderGLSL = `#version 450 layout(location = 0) out vec4 outColor; void main() { outColor = vec4(1.0, 0.0, 0.0, 1.0); } `;
这里是vertex shader和fragment shader的glsl代码。数组
(webgpu支持vertex shader、fragment shader、compute shader,这里只使用了前面两个)ide
“#version 450”声明了glsl版本为4.5(它要放在glsl的第一行)函数
第2行定义了三角形的三个顶点坐标,使用2维数组保存(每一个元素为vec2类型)。由于都在一个平面,因此顶点只定义了x、y坐标(顶点的z为0.0)布局
第5行的gl_VertexIndex为顶点序号,每次执行时值依次为0、一、2(vertex shader被执行了3次,由于只有3个顶点)(具体见本文末尾对draw的分析)
第9行是fragment shader,由于三角形为一个颜色,因此全部片断的颜色为同一个固定值
const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); // 准备编译glsl的库 const glslang = await glslangModule(); // 得到webgpu上下文 const context = canvas.getContext('gpupresent');
第4行的glslangModule是import的第三方库:
import glslangModule from '../glslang';
// 定义swapbuffer的格式为RGBA8位的无符号归一化格式 const swapChainFormat = "bgra8unorm"; // @ts-ignore: const swapChain: GPUSwapChain = context.configureSwapChain({ device, format: swapChainFormat, });
@ts-ignore是typescript用来忽略错误的。由于context的类型是RenderingContext,它没有定义configureSwapChain函数,若是编译该行typescript会报错,因此须要忽略错误。
第5行配置了swap chain。vulkan tutorial对此进行了说明:
swap chain是一个缓冲结构,webgpu会先将内容渲染到swap chain的buffer中,而后再将其显示到屏幕上;
swap chain本质上是等待呈如今屏幕上的一个图片队列。
const pipeline = device.createRenderPipeline({ layout: device.createPipelineLayout({ bindGroupLayouts: [] }), vertexStage: { module: device.createShaderModule({ code: glslang.compileGLSL(vertexShaderGLSL, "vertex"), // @ts-ignore source: vertexShaderGLSL, transform: source => glslang.compileGLSL(source, "vertex"), }), entryPoint: "main" }, fragmentStage: { module: device.createShaderModule({ code: glslang.compileGLSL(fragmentShaderGLSL, "fragment"), // @ts-ignore source: fragmentShaderGLSL, transform: source => glslang.compileGLSL(source, "fragment"), }), entryPoint: "main" }, primitiveTopology: "triangle-list", colorStates: [{ format: swapChainFormat, }], });
WebGPU有两种pipeline:render pipeline和compute pipeline,这里只用了render pipeline
这里使用render pipeline descriptor来建立render pipeline,它的定义以下:
dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase { required GPUPipelineLayout layout; }; ... dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase { required GPUProgrammableStageDescriptor vertexStage; GPUProgrammableStageDescriptor fragmentStage; required GPUPrimitiveTopology primitiveTopology; GPURasterizationStateDescriptor rasterizationState = {}; required sequence<GPUColorStateDescriptor> colorStates; GPUDepthStencilStateDescriptor depthStencilState; GPUVertexStateDescriptor vertexState = {}; unsigned long sampleCount = 1; unsigned long sampleMask = 0xFFFFFFFF; boolean alphaToCoverageEnabled = false; // TODO: other properties };
render pipeline能够设置绑定的资源布局、编译的shader、fixed functions(如混合、深度、模版、cullMode等各类状态和顶点数据的格式vertexState),相对于WebGL(WebGL的一个API只能设置一个,如使用gl.cullFace设置cull mode),提高了性能(静态设置了各类状态,不须要在运行时设置),便于管理(把各个状态集中到了一块儿设置)。
vertexStage和fragmentStage分别设置vertex shader和fragment shader:
使用第三方库,将glsl编译为字节码(格式为SPIR-V);
source和transform字段是多余的,能够删除。
由于shader没有绑定资源(如uniform buffer, texture等),因此第2行的bindGroupLayouts为空数组,不须要bind group和bind group layout
第25行的primitiveTopology指定片元的拓扑结构,此处为三角形。
它能够为如下值:
enum GPUPrimitiveTopology { "point-list", "line-list", "line-strip", "triangle-list", "triangle-strip" };
如今先忽略colorStates
frame函数定义了每帧执行的逻辑:
function frame() { const commandEncoder = device.createCommandEncoder({}); const textureView = swapChain.getCurrentTexture().createView(); const renderPassDescriptor: GPURenderPassDescriptor = { colorAttachments: [{ attachment: textureView, loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, }], }; const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); passEncoder.setPipeline(pipeline); passEncoder.draw(3, 1, 0, 0); passEncoder.endPass(); device.defaultQueue.submit([commandEncoder.finish()]); } return frame;
咱们不能直接操做command buffer,须要建立command encoder,使用它将多个commands(如render pass的draw)设置到一个command buffer中,而后执行submit,把command buffer提交到gpu driver的队列中。
根据 webgpu设计文档->Command Submission:
Command buffers carry sequences of user commands on the CPU side. They can be recorded independently of the work done on GPU, or each other. They go through the following stages:
creation -> "recording" -> "ready" -> "executing" -> done
咱们知道,command buffer有
creation, recording,ready,executing,done五种状态。
根据该文档,结合代码来分析command buffer的操做流程:
第2行建立command encoder时,应该是建立了command buffer,它的状态为creation;
第12行开始render pass(webgpu还支持compute pass,不过这里没用到),command buffer的状态变为recording;
13-14行将“设置pipeline”、“绘制”的commands设置到command buffer中;
第15行结束render pass,(能够设置下一个pass,如compute pass,不过这里只用了一个pass);
第17行“commandEncoder.finish()”将command buffer的状态变为ready;
而后执行subimit,command buffer状态变为executing,被提交到gpu driver的队列中,不能再在cpu端被操做;
若是提交成功,gpu会决定在某个时间处理它。
第5行的renderPassDescriptor描述了render pass,它的定义为:
dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { required sequence<GPURenderPassColorAttachmentDescriptor> colorAttachments; GPURenderPassDepthStencilAttachmentDescriptor depthStencilAttachment; };
这里只用到了colorAttachments。它相似于WebGL->framebuffer的colorAttachments。这里只用到了一个color buffer attachment。
咱们来看下colorAttachment的定义:
dictionary GPURenderPassColorAttachmentDescriptor { required GPUTextureView attachment; GPUTextureView resolveTarget; required (GPULoadOp or GPUColor) loadValue; GPUStoreOp storeOp = "store"; };
这里设置attachment,将其与swap chain关联:
attachment: textureView,
咱们如今忽略resolveTarget。
loadValue和storeOp决定渲染前和渲染后怎样处理attachment中的数据。
咱们看下它的类型:
enum GPULoadOp { "load" }; enum GPUStoreOp { "store", "clear" }; ... dictionary GPUColorDict { required double r; required double g; required double b; required double a; }; typedef (sequence<double> or GPUColorDict) GPUColor;
loadValue若是为GPULoadOp类型,则只有一个值:“load”,它的意思是渲染前保留attachment中的数据;
若是为GPUColor类型(如这里的{ r: 0.0, g: 0.0, b: 0.0, a: 1.0 }),则不只为"load",并且设置了渲染前的初始值,相似于WebGL的clearColor。
storeOp若是为“store”,意思是渲染后保存被渲染的内容到内存中,后面能够被读取;
若是为“clear”,意思是渲染后清空内容。
如今咱们回头看下render pipeline中的colorStates:
colorStates: [{ format: swapChainFormat, }],
colorStates与colorAttachments对应,也只有一个,它的format应该与swap chain的format相同
咱们继续看render pass代码:
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); passEncoder.setPipeline(pipeline); passEncoder.draw(3, 1, 0, 0); passEncoder.endPass();
draw的定义为:
void draw(unsigned long vertexCount, unsigned long instanceCount, unsigned long firstVertex, unsigned long firstInstance);
三角形有3个顶点,这里只绘制1个实例,二者都从0开始(因此vertex shader中的gl_VertexIndex依次为0、一、2),因此第3行为“draw(3, 1, 0, 0)”
webgpu-samplers Github Repo
vulkan tutorial
webgpu设计文档->Command Submission
WebGPU-4