WebGPU进修(二): 进修“绘制一个三角形”示例_腾讯云双十一,阿里云

  • WebGPU进修(二): 进修“绘制一个三角形”示例_腾讯云双十一,阿里云已关闭评论
  • 273 人浏览
  • A+
所属分类:教程分享 首页

大家好,本文进修Chrome->webgl-samplers->helloTriangle示例。

上一篇文章:WebGPU进修(一): 开篇

预备Sample代码

克隆webgl-samplers Github Repo到当地。
(备注:当前的version为0.0.2)

现实的sample代码在src/examples/文件夹中,是typescript代码写的:
WebGPU进修(二): 进修“绘制一个三角形”示例_腾讯云双十一,阿里云

进修helloTriangle.ts

翻开helloTriangle.ts文件,我们来看下init函数的内容。

首先是shader代码

    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,这里只运用了前面两个)

“#version 450”声清楚明了glsl版本为4.5(它要放在glsl的第一行)

第2行定义了三角形的三个极点坐标,运用2维数组保存(每一个元素为vec2范例)。由于都在一个平面,所以极点只定义了x、y坐标(极点的z为0.0)

第5行的gl_VertexIndex为极点序号,每次实行时价依次为0、1、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本质上是守候呈如今屏幕上的一个图片行列。

接下来就是建立render pipeline

    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,
      }],
    });

相识pipeline

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),提升了机能(静态设置了种种状况,不须要在运行时设置),便于管理(把各个状况集合到了一同设置)。

剖析render pipeline descriptor

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函数

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 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会决议在某个时候处置惩罚它。

剖析render pass

第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、1、2),所以第3行动“draw(3, 1, 0, 0)”

终究衬着效果

WebGPU进修(二): 进修“绘制一个三角形”示例_腾讯云双十一,阿里云

参考资料

webgl-samplers Github Repo
vulkan tutorial
webgpu设想文档->Command Submission
WebGPU-4

腾讯云双十一活动