大家好,本文学习Chrome->webgpu-samplers->helloTriangle示例。

上一篇博文:

WebGPU学习(一): 开篇

下一篇博文:

WebGPU学习(三):MSAA

准备Sample代码

克隆webgpu-samplers Github Repo到本地。

(备注:当前的version为0.0.2)

实际的sample代码在src/examples/文件夹中,是typescript代码写的:

学习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-samplers Github Repo

vulkan tutorial

webgpu设计文档->Command Submission

WebGPU-4

WebGPU学习(二): 学习“绘制一个三角形”示例的更多相关文章

  1. Unity3D学习笔记1——绘制一个三角形

    目录 1. 绪论 2. 概述 3. 详论 3.1. 准备 3.2. 实现 3.3. 解析 3.3.1. 场景树对象 3.3.2. 绘制方法 4. 结果 1. 绪论 最近想学习一下Unity3d,无奈发 ...

  2. Android快乐贪吃蛇游戏实战项目开发教程-03虚拟方向键(二)绘制一个三角形

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.绘制三角形 在上一篇文章中,我们已经新建了虚拟方向键的自定义控件Direct ...

  3. Unity3D学习笔记2——绘制一个带纹理的面

    目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...

  4. WCF学习之旅——第一个WCF示例(二)

    第四步:通过自我寄宿的方式寄宿服务 WCF服务需要依存一个运行着的进程(宿主),服务寄宿就是为服务指定一个宿主的过程.WCF是一个基于消息的通信框架,采用基于终结点(Endpoint)的通信手段. 终 ...

  5. WCF学习之旅——第一个WCF示例(一)

    最近需要用到WCF,所以对WCF进行了解.在实践中学习新知识是最快的,接下来先做了一个简单的WCF服用应用示例. 本文的WCF服务应用功能很简单,却涵盖了一个完整WCF应用的基本结构.希望本文能对那些 ...

  6. WCF学习之旅——第一个WCF示例(三)

    第五步:创建客户端 WCF应用服务被成功寄宿后,WCF服务应用便开始了服务调用请求的监听工作.此外,服务寄宿将服务描述通过元数据的形式发布出来,相应的客户端就可以获取这些元数据.接下来我们来创建客户端 ...

  7. WebGL简易教程(三):绘制一个三角形(缓冲区对象)

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  8. 【OpenGL4.0】GLSL渲染语言入门与VBO、VAO使用:绘制一个三角形 【转】

    http://blog.csdn.net/xiajun07061225/article/details/7628146 以前都是用Cg的,现在改用GLSL,又要重新学,不过两种语言很多都是相通的. 下 ...

  9. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

随机推荐

  1. Linux CentOS7部署ASP.NET Core应用程序,并配置Nginx反向代理服务器

    前言: 本篇文章主要讲解的是如何在Linux CentOS7操作系统搭建.NET Core运行环境并发布ASP.NET Core应用程序,以及配置Nginx反向代理服务器.因为公司的项目一直都是托管在 ...

  2. 搭建邮件服务器,使用Postfix与Dovecot收发电子邮件

    小知识: 我们为什么要搭建邮件服务器呢?有时候我们处于一个局域网内,不能及时的分享各自的研究成果,迫切的需要一种能够借助于网络且建立在计算机之间的传输数据的方法.所以我们需要搭建邮件服务器,这样的话既 ...

  3. 模板(ac):启发式合并

    首先说明一点:线段树合并不是启发式合并. 启发式合并的大概内容就是:把小的数据结构按照这个数据结构的正常插入方法,一个一个地暴力塞进去. 而线段树合并显然不是这个东西. 这道题的题解太烂了,所以耽误了 ...

  4. python基础-函数作用域

    函数 函数对象 函数是第一类对象 函数名可以被引用 函数名可以当作参数使用 函数名可以当作返回值使用 函数名可以当作容器类型的元素 函数嵌套 嵌套调用:在函数内部中调用函数 嵌套定义:在函数内部中定义 ...

  5. 【RocketMQ源码学习】- 3. Client 发送同步消息

    本文较长,代码后面给了方法简图,希望给你帮助 发送的方式 同步发送 异步发送 消息的类型 普通消息 顺序消息 事务消息 发送同步消息的时序图 为了防止读者朋友嫌烦,可以看下时序图,后面我也会给出方法的 ...

  6. python之带有参数的装饰器

    一个小demo def set_level(level_num): def set_func(func): def call_func(*args, **kwargs): if level_num = ...

  7. Linux修改主机名!(图文)

    本篇作为之前的补充篇,如果想修改自己的主机名,方便老师检查作业是否是自己做的,可以用修改主机名的方法,那么怎么修改呢? 一. 使用hostname命令 比如我现在的主机名是haozhikuan-hbz ...

  8. NOIP 模拟29 B 侥幸

    这次考得好纯属是侥幸,我T3打表试数试了两个小时,没有想打T2的正解(其实是打不出来)所以这个T3A掉纯属是侥幸,以后还是要打正解 (以下博客最好按全选观看,鬼知道为啥这个样子!) 在这里也口胡一下我 ...

  9. ORACLE存储过程的创建和执行的简单示例和一些注意点

    此示例的主要目的主要是为了了解在PL/SQL环境下怎么创建和执行存储过程. 存储过程所涉及的DataTable: 第一步:创建游标变量 游标是ORACLE系统在内存中开辟的一个工作区,主要用来存储SE ...

  10. django 之创建自己的模板(使用案例)

    Django 创建自己的模板篇(实例) 此处需要创建模板,主要是对自己的模板进行扩展: 一般是扩展模板的tag和filter两个功能.可以用来创建你自己的tag和filter功能库. 创建模板库 分为 ...