/src/basicTriangle.ts
在Html页面中添加Canvas画布,用CSS占满全屏
<head>
<style>
canvas {
width:100%;
height:100%;
}
style>
head>
<body>
<canvas>canvas>
<script> type="module" scr="/src/basicTriangle.ts"script>
body>
async function initwebgpu(){
if(!navigator.gpu)
throw new Error()
// 请求一个GPUAdapter对象(浏览器对WebGPU的抽象代理,不能直接操作),返回promise【异步API】
const adapter = await navigator.gpu.requestAdapter({
// 设置的是一个期望值,更多是一个flag,一般可以不设置或者暴力设置为高性能
powerPreference:'high-performance'
})
// 请求一个具体的、可操作的逻辑实例【API】
if(!adapter)
throw new Error() //避免device为空
const device = await adapter.requestDevice({
requiredFeatures:['text'],
requiredLimits:{
maxStorageBufferBindingSize=adapter.limits.maxStorageBufferBindingSize
}
})
/**
* 可以通过Adapter读取浏览器对WebGPU实现了哪些功能和参数,但不能直接用它进行计算和绘制图形
* 打印adapter,device的具体参数
* consle.log(adapter,device)
* adapter主要包含两个可读的对象:features、limits
* limits:当前Web浏览器对WebGPU支持的一些参数的最大值
* features:当前浏览器实现了哪些扩展功能
* 遍历查看当前浏览器的扩展功能
* adapter.features.forEach(v=>{
console.log(v)
})
* device和adapter的limits参数可能不一样,可在requestDevice()申请拓展功能和修改参数
*/
//获得canvas对象,但是不能直接画图,它只是一个HTML的DOM节点
const canvas = document.querySelector('canvas')
//获取可以直接通过JS操作的逻辑画布
if(!canvas)
throw new Errow()
const context = canvas.getContext('webgpu')
//利用configure API配置画布,至少需要device和format参数
//用getPreferredFormat API得到浏览器默认的颜色选项
if(!context)
throw new Errow()
const format = context.getPreferredFormat(adapter)
const size = [canvas.clientWidth * window.devicePixelRatio, canvas.clientHeight * window.devicePixelRatio]
context?.configure({
device,format,size,
compositingAlphaMode:'opaque'
})
/**
* 打印浏览器默认格式
* bgra8unorm:以0-1小数表示的BGRA格式,支持大部分浏览器
*/
console.log(format)
// json在JS的简写形式:如果变量名和参数名一样的情况下(adapter:adapter),可以省略冒号后面的部分,以此简化代码,提高可读性
return(adapter,device,context,format)
}
// initwebgpu() 测试时使用
AsyncFunction 构造函数用来创建新的 异步函数 对象,JavaScript 中每个异步函数都是 AsyncFunction 的对象。
注意,AsyncFunction 并不是一个全局对象,需要通过下面的方法来获取:
Object.getPrototypeOf(async function(){}).constructor
async 函数是使用async关键字声明的函数。 async 函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function
点→线→三角面→多边形
开发者需要操作且只能控制的阶段只有:Vertx Shader和Fragment Shader
管线的本质:依次运行一些程序(Shader)
// 通过import把.wgsl当做单独的模块引用进来
import vertex from './shaders/triangle.vert.wgsl?raw'
// import默认无法引用纯字符串,必须是一种js或typescript文件,有export的对象才可以,否则ts无法解析这句话
// 利用vite语法环境特性,在后面加上?raw,就可以把纯文本直接引用并赋值给vert
// 在url后面加?raw既不是import标准语法,也不是typescript的语法,而是因为我们在tsconfig.json中引入了@vite/client这个定义包,这是vite加载字符串语法
import frag from './shaders/red.frag.wgsl?raw'
async function initwebgpu(){- //接上文初始化
}
async function initpipeline(device:GPUDevice,format:GPUTextrueFormat){ //typescript语法:需要声明device类型,否则没有提示
// 根据index的不同输出三个不同点的信息
const vertxShader = device.createShaderModule({
// WGSL代码。WGSL是W3C为WebGpu制定的一套全新的Shading Language,和目前主流的Shader并没有实质区别
// 一般不推荐直接把代码用字符串的形式写在函数里,可读性差也不便管理
code:'vertex'
})
// 输出颜色
const fragmentShader = device.createShaderModule({
code:'frag'
})
// 创建并可返回pipeline的管线,有两种形式(同步、异步),在WebGPU推荐有异步用异步,尽量避免JS形成的挂起等待
const pipeline = await device.createRenderPipelineAsync({
vertex:{
module:vertxShader,
entryPoint:'main' //Shader的入口函数
},
fragment:{
module:fragmentShader,
entryPoint:'main',
targets:[{
format
}] //表明输出的颜色格式,并且与画布设置的格式匹配
}
//绘图方式
primitive:{
topology:'triangle-list'
}
})
return {pipeline}
}
async function run(){
const {device,format} = await initwebgpu(),
const {pipeline} = await initpipeline(device,format)
}
function draw(device:GPUDevice,pipeline:GPURenderPipeline,context:GPUCanvasContext){
//WebGPU的commandEncoder机制:把所有命令提前写入encoder当中,再进行一次性的提交给Native运行
//创建Encoder对象(不是异步API,本质上没有跟WebGPU做复杂的交互)
const encoder = device.createCommandEncoder()
//..命令录制
//beginRenderpass:类似于绘制图层,常用于特效、阴影的绘制。与encoder类似,返回一个Renderpass的object
//Renderpass的结果会对应到相应的Canvas上
const renderPass = encoder.beginRenderpass({
//...Renderpass命令录制
//清空画布,设置背景色
//颜色附件:如何处理颜色信息
colorAttachments:[{
//view:设置通道输出在哪显示
//createview:获取一个可以被GPU操作的view buffer
view:context.getCurrentTexture().createView(),
//loadOp:在绘制前是否加载当前view的内容
//clear:清空内容然后再绘制,相当于添加一个背景颜色or清空画布
//load:再原有内容上接着绘制
loadOp:'clear',
//设置背景色(绘制前对view的操作)
clearValue:{r:0,g:0,b:0,a:1}, //黑色
//(绘制后对view的操作)
//discard:丢弃结果,store:保留结果
storeOp:'store'
}]
})
//加载pipeline(管线)
renderPass.setPipeline(pipeline)
/* 实际场景可能设计多个pipeline和多个物体
* 可以通过for循环遍历每个物体,动态设置每个物体对应的pipeline和draw()它们对应的顶点数
* for(let i = 0; i< 10000; i++)
* renderPass.draw(4)
*/
//运行管线
//用多少个线程去运行vertexShader,一般画什么图形的对应顶点数量
renderPass.draw(3)
/* pipeline中的primitive的topology参数(vertex输出的顶点信息该怎么组合)
* triangle-list:三个点形成三角面,四边形则需要6个点,draw(6)
* point-list:将每一个点当做独立的点进行输出
* line-list:每两个点组成一条线,1&2、3&4
* line-strip:首尾连接的组合模式,1&2、2@3
* triangle-strip:首尾连接的组合模式,1&2&3、234、345,四个点就可以绘制出四边形,两个三角面共用一条边,draw的参数也可以为5甚至是4,但是对顶点顺序要求很高,而且需要对其优化
*/
//结束这个通道的Renderpass录制工作
renderPass.end()
//结束录制,返回buffer
const buffer = encoder.finish()
//将buffer传递给Dawn,Dawn在浏览器里操作GPU
device.queue.submit([buffer])
//submit之前,命令录制里的所有命令都没有执行、Dawn和GPU都没有真正工作,此时js线程是空闲的
//submit也不是异步的API,但整个绘制过程相对于JS而言是一个完全独立的异步过程,这是与WebGL的同步操作的本质区别,也是WebGPU的核心特性
}
//上节课的
async function run(){
const {device,format,context} = await initwebgpu()
const {pipeline} = await initpipeline(device,format)
//这节课的
draw(device,pipeline,context)
}