你們好,本文學習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