五、WebGPU Vertex Buffers 顶点缓冲区

五、WebGPU Vertex Buffers 顶点缓冲区

在上一篇文章中,我们将顶点数据放入存储缓冲区中,并使用内置的vertex_index对其进行索引。虽然这种技术越来越受欢迎,但向顶点着色器提供顶点数据的传统方式是通过顶点缓冲和属性。

顶点缓冲区就像任何其他WebGPU缓冲区一样。它们保存着数据。不同之处在于我们不直接从顶点着色器访问它们。相反,我们告诉WebGPU缓冲区中有什么类型的数据,以及它在哪里以及它是如何组织的。然后它将数据从缓冲区中取出并提供给我们。

让我们以上一篇文章中的最后一个示例为例,将其从使用存储缓冲区更改为使用顶点缓冲区。

首先要做的是改变着色器,从顶点缓冲区中获取顶点数据。

struct OurStruct {color: vec4f,offset: vec2f,
};struct OtherStruct {scale: vec2f,
};struct Vertex {@location(0) position: vec2f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
};@group(0) @binding(0) var<storage, read> ourStructs: array<OurStruct>;
@group(0) @binding(1) var<storage, read> otherStructs: array<OtherStruct>;@vertex fn vs(vert: Vertex,@builtin(instance_index) instanceIndex: u32
) -> VSOutput {let otherStruct = otherStructs[instanceIndex];let ourStruct = ourStructs[instanceIndex];var vsOut: VSOutput;vsOut.position = vec4f(vert.position * otherStruct.scale + ourStruct.offset, 0.0, 1.0);vsOut.color = ourStruct.color;return vsOut;
}...

正如你所看到的,这是一个很小的变化。我们声明了一个结构体Vertex来定义顶点的数据。重要的部分是用@location(0)声明位置字段,然后,当我们创建渲染管道时,我们必须告诉WebGPU如何获取@location(0)的数据。

然后,当我们创建渲染管道时,我们必须告诉WebGPU如何获取@location(0)的数据。

  const pipeline = device.createRenderPipeline({label: 'vertex buffer pipeline',layout: 'auto',vertex: {module,entryPoint: 'vs',buffers: [{arrayStride: 2 * 4, // 2 floats, 4 bytes eachattributes: [{shaderLocation: 0, offset: 0, format: 'float32x2'},  // position],},],},fragment: {module,entryPoint: 'fs',targets: [{ format: presentationFormat }],},});

对于pipeline descriptor 的 vertex entry,我们添加了一个缓冲区数组,用于描述如何从一个或多个顶点缓冲区中提取数据。对于第一个也是唯一一个缓冲区,我们以字节数为单位设置arrayStride。在这种情况下,步长是指从缓冲区中一个顶点的数据到缓冲区中的下一个顶点的字节数。

在这里插入图片描述

因为我们的数据是vec2f,这是两个float32数字,所以我们将arrayStride设置为8。

接下来我们定义一个属性数组。我们只有一个。shaderLocation: 0对应于我们顶点结构中的location(0)。offset: 0表示此属性的数据从顶点缓冲区中的第0字节开始。最后format:'float32x2’表示我们希望WebGPU将数据从缓冲区中取出为两个32位浮点数。

我们需要将保存顶点数据的缓冲区的用法从STORAGE更改为vertex,并将其从绑定组中删除。

  const vertexBuffer = device.createBuffer({label: 'vertex buffer vertices',size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,});device.queue.writeBuffer(vertexBuffer, 0, vertexData);const bindGroup = device.createBindGroup({label: 'bind group for objects',layout: pipeline.getBindGroupLayout(0),entries: [{ binding: 0, resource: { buffer: staticStorageBuffer }},{ binding: 1, resource: { buffer: changingStorageBuffer }},],});

然后在绘制时,我们需要告诉webgpu使用哪个顶点缓冲区。

 pass.setPipeline(pipeline);pass.setVertexBuffer(0, vertexBuffer);

这里的0对应于我们上面指定的渲染管道缓冲区数组的第一个元素。

这样我们就从顶点存储缓冲区切换到了顶点缓冲区。

以下为完整代码及运行结果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-09-19 22:06:18* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="007vertex-srandom-circle"><canvas id="gpucanvas"></canvas></div><script type="module" src="./007vertex-srandom-circle.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-09-19 22:12:42* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";const rand = (min: undefined | number = undefined,max: undefined | number = undefined
) => {if (min === undefined) {min = 0;max = 1;} else if (max === undefined) {max = min;min = 0;}return min + Math.random() * (max - min);
};function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2
} = {}) {// 2 triangles per subdivision, 3 verts per tri, 2 values(xy) eachconst numVertices = numSubdivisions * 3 * 2;const vertexData = new Float32Array(numSubdivisions * 2 * 3 * 2);let offset = 0;const addVertex = (x: number, y: number) => {vertexData[offset++] = x;vertexData[offset++] = y;};// 2 vertices per subdivisionfor (let i = 0; i < numSubdivisions; ++i) {const angle1 =startAngle + ((i + 0) * (endAngle - startAngle)) / numSubdivisions;const angle2 =startAngle + ((i + 1) * (endAngle - startAngle)) / numSubdivisions;const c1 = Math.cos(angle1);const s1 = Math.sin(angle1);const c2 = Math.cos(angle2);const s2 = Math.sin(angle2);// first angleaddVertex(c1 * radius, s1 * radius);addVertex(c2 * radius, s2 * radius);addVertex(c1 * innerRadius, s1 * innerRadius);// second triangleaddVertex(c1 * innerRadius, s1 * innerRadius);addVertex(c2 * radius, s2 * radius);addVertex(c2 * innerRadius, s2 * innerRadius);}return {vertexData,numVertices};
}const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "hardcoded rgb triangle pipeline",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs",buffers: [{arrayStride: 2 * 4, // 2 floats, 4 bytes eachattributes: [{shaderLocation: 0, offset: 0,format: "float32x2"} // position]}]},fragment: {module: shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});const kNumObjects = 100;const staticStorageUnitSize =4 * 4 + // color is 4 32bit floats (4bytes each)2 * 4 + // scale is 2 32bit floats (4bytes each)2 * 4; // paddingconst storageUnitSzie = 2 * 4; // scale is 2 32 bit floatsconst staticStorageBufferSize = staticStorageUnitSize * kNumObjects;const storageBufferSize = storageUnitSzie * kNumObjects;const staticStorageBuffer = device.createBuffer({label: "static storage for objects",size: staticStorageBufferSize,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST});const storageBuffer = device.createBuffer({label: "changing storage for objects",size: storageBufferSize,usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST});const staticStorageValues = new Float32Array(staticStorageBufferSize / 4);const storageValues = new Float32Array(storageBufferSize / 4);const kColorOffset = 0;const kOffsetOffset = 4;const kScaleOffset = 0;const objectInfos: {scale: number;}[] = [];for (let i = 0; i < kNumObjects; ++i) {const staticOffset = i * (staticStorageUnitSize / 4);staticStorageValues.set([rand(), rand(), rand(), 1],staticOffset + kColorOffset);staticStorageValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)],staticOffset + kOffsetOffset);objectInfos.push({scale: rand(0.2, 0.5)});}device.queue.writeBuffer(staticStorageBuffer, 0, staticStorageValues);const { vertexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25});const vertexBuffer = device.createBuffer({label: "vertex buffer vertices",size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});device.queue.writeBuffer(vertexBuffer, 0, vertexData);const bindGroup = device.createBindGroup({label: "bind group for objects",layout: renderPipeline.getBindGroupLayout(0),entries: [{ binding: 0, resource: { buffer: staticStorageBuffer } },{ binding: 1, resource: { buffer: storageBuffer } }]});function frame() {const aspect = canvas.width / canvas.height;const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {label: "our basic canvas renderPass",colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0,vertexBuffer);objectInfos.forEach(({ scale }, ndx) => {const offset = ndx * (storageUnitSzie / 4);storageValues.set([scale / aspect, scale], offset + kScaleOffset); // set the scale});device.queue.writeBuffer(storageBuffer, 0, storageValues);renderPass.setBindGroup(0, bindGroup);renderPass.draw(numVertices, kNumObjects);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurStruct {color: vec4f,offset: vec2f
};struct OtherStruct {scale: vec2f
};struct Vertex {@location(0) position: vec2f
}struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
};@group(0) @binding(0) var<storage,read> ourStructs: array<OurStruct>;
@group(0) @binding(1) var<storage,read> otherStructs: array<OtherStruct>;
@vertex 
fn vs(vert: Vertex, @builtin(instance_index) instanceIndex: u32) -> VSOutput {let otherStruct = otherStructs[instanceIndex];let ourStruct = ourStructs[instanceIndex];var vsOut: VSOutput;vsOut.position = vec4f(vert.position * otherStruct.scale + ourStruct.offset, 0.0, 1.0);vsOut.color = ourStruct.color;return vsOut;
}@fragment
fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

在这里插入图片描述

在这里插入图片描述

执行draw命令时的状态如下所示:

在这里插入图片描述

属性格式字段可以是这些类型之一:

Vertex formatData typeComponentsByte sizeExample WGSL type
"uint8x2"unsigned int22vec2<u32>, vec2u
"uint8x4"unsigned int44vec4<u32>, vec4u
"sint8x2"signed int22vec2<i32>, vec2i
"sint8x4"signed int44vec4<i32>, vec4i
"unorm8x2"unsigned normalized22vec2<f32>, vec2f
"unorm8x4"unsigned normalized44vec4<f32>, vec4f
"snorm8x2"signed normalized22vec2<f32>, vec2f
"snorm8x4"signed normalized44vec4<f32>, vec4f
"uint16x2"unsigned int24vec2<u32>, vec2u
"uint16x4"unsigned int48vec4<u32>, vec4u
"sint16x2"signed int24vec2<i32>, vec2i
"sint16x4"signed int48vec4<i32>, vec4i
"unorm16x2"unsigned normalized24vec2<f32>, vec2f
"unorm16x4"unsigned normalized48vec4<f32>, vec4f
"snorm16x2"signed normalized24vec2<f32>, vec2f
"snorm16x4"signed normalized48vec4<f32>, vec4f
"float16x2"float24vec2<f16>, vec2h
"float16x4"float48vec4<f16>, vec4h
"float32"float14f32
"float32x2"float28vec2<f32>, vec2f
"float32x3"float312vec3<f32>, vec3f
"float32x4"float416vec4<f32>, vec4f
"uint32"unsigned int14u32
"uint32x2"unsigned int28vec2<u32>, vec2u
"uint32x3"unsigned int312vec3<u32>, vec3u
"uint32x4"unsigned int416vec4<u32>, vec4u
"sint32"signed int14i32
"sint32x2"signed int28vec2<i32>, vec2i
"sint32x3"signed int312vec3<i32>, vec3i
"sint32x4"signed int416vec4<i32>, vec4i

实例化顶点缓冲区

属性可以按顶点或实例推进。在每个实例中推进它们实际上是我们在索引otherStructs[instanceIndex]和ourStructs[instanceIndex]时所做的相同的事情,其中instanceIndex从@builtin(instance_index)获得其值。

让我们去掉存储缓冲区,使用顶点缓冲区来完成同样的事情。首先让我们改变着色器使用顶点属性而不是存储缓冲区。

struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
};@group(0) @binding(0) var<storage, read> ourStructs: array<OurStruct>;
@group(0) @binding(1) var<storage, read> otherStructs: array<OtherStruct>;@vertex fn vs(vert: Vertex,
) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color;return vsOut;
}@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

现在我们需要更新渲染管道,告诉它我们希望如何向这些属性提供数据。为了保持最小的变化,我们将使用我们为存储缓冲区创建的数据。我们将使用两个缓冲区,一个缓冲区将保存每个实例的颜色和偏移量,另一个将保存比例。

  const pipeline = device.createRenderPipeline({label: 'flat colors',layout: 'auto',vertex: {module,entryPoint: 'vs',buffers: [{arrayStride: 2 * 4, // 2 floats, 4 bytes eachattributes: [{shaderLocation: 0, offset: 0, format: 'float32x2'},  // position],},{arrayStride: 6 * 4, // 6 floats, 4 bytes eachstepMode: 'instance',attributes: [{shaderLocation: 1, offset:  0, format: 'float32x4'},  // color{shaderLocation: 2, offset: 16, format: 'float32x2'},  // offset],},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: 'instance',attributes: [{shaderLocation: 3, offset: 0, format: 'float32x2'},   // scale],},],},fragment: {module,entryPoint: 'fs',targets: [{ format: presentationFormat }],},});

上面我们在流水线描述中向buffers数组中添加了2个条目,所以现在有3个缓冲区条目,这意味着我们告诉WebGPU我们将在3个缓冲区中提供数据。

对于我们的两个新实体,我们将stepMode设置为instance。这意味着该属性在每个实例中只前进到下一个值一次。默认是stepMode: ‘vertex’,每个顶点前进一次(每个实例重新开始)。

我们有2个缓冲区。保持比例的那个很简单。就像我们第一个保存位置的缓冲区一样,每个顶点有2*32个浮点数。

另一个缓冲区保存颜色和偏移量它们将像这样在数据中交错:

在这里插入图片描述

所以上面我们说从一组数据到下一组数据的arrayStride是6 * 4,6个32位浮点数,每4字节(总共24字节)。颜色从偏移量0开始,但偏移量从16字节开始。

接下来,我们可以更改设置缓冲区的代码。

  // create 2 storage buffersconst staticUnitSize =4 * 4 + // color is 4 32bit floats (4bytes each)2 * 4;  // offset is 2 32bit floats (4bytes each)const changingUnitSize =2 * 4;  // scale is 2 32bit floats (4bytes each)const staticVertexBufferSize = staticUnitSize * kNumObjects;const changingVertexBufferSize = changingUnitSize * kNumObjects;const staticVertexBuffer = device.createBuffer({label: 'static vertex for objects',size: staticVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,});const changingVertexBuffer = device.createBuffer({label: 'changing vertex for objects',size: changingVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,});

顶点属性与存储缓冲区中的结构没有相同的填充限制,所以我们不再需要填充。否则,我们所做的就是将用法从STORAGE更改为VERTEX(我们将所有变量从“STORAGE”重命名为“VERTEX”)。

由于我们不再使用存储缓冲区,因此不再需要bindGroup。

最后,我们不需要设置bindGroup但我们需要设置顶点缓冲区:

 const encoder = device.createCommandEncoder();const pass = encoder.beginRenderPass(renderPassDescriptor);pass.setPipeline(pipeline);pass.setVertexBuffer(0, vertexBuffer);pass.setVertexBuffer(1, staticVertexBuffer);pass.setVertexBuffer(2, changingVertexBuffer);...pass.draw(numVertices, kNumObjects);pass.end();

在这里,setVertexBuffer的第一个参数对应于我们上面创建的管道中的buffers数组的元素。

这和我们之前做的是一样的但是我们用的都是顶点缓冲区,没有存储缓冲区。

以下为完整代码及运行结果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-09-19 22:06:18* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="007vertex-srandom-circle"><canvas id="gpucanvas"></canvas></div><script type="module" src="./007vertex-srandom-circle2.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-10-08 22:47:31* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";const rand = (min: undefined | number = undefined,max: undefined | number = undefined
) => {if (min === undefined) {min = 0;max = 1;} else if (max === undefined) {max = min;min = 0;}return min + Math.random() * (max - min);
};function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2
} = {}) {// 2 triangles per subdivision, 3 verts per tri, 2 values(xy) eachconst numVertices = numSubdivisions * 3 * 2;const vertexData = new Float32Array(numSubdivisions * 2 * 3 * 2);let offset = 0;const addVertex = (x: number, y: number) => {vertexData[offset++] = x;vertexData[offset++] = y;};// 2 vertices per subdivisionfor (let i = 0; i < numSubdivisions; ++i) {const angle1 =startAngle + ((i + 0) * (endAngle - startAngle)) / numSubdivisions;const angle2 =startAngle + ((i + 1) * (endAngle - startAngle)) / numSubdivisions;const c1 = Math.cos(angle1);const s1 = Math.sin(angle1);const c2 = Math.cos(angle2);const s2 = Math.sin(angle2);// first angleaddVertex(c1 * radius, s1 * radius);addVertex(c2 * radius, s2 * radius);addVertex(c1 * innerRadius, s1 * innerRadius);// second triangleaddVertex(c1 * innerRadius, s1 * innerRadius);addVertex(c2 * radius, s2 * radius);addVertex(c2 * innerRadius, s2 * innerRadius);}return {vertexData,numVertices};
}const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "flat colors",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs",buffers: [{arrayStride: 2 * 4, // 2 floats, 4 bytes eachattributes: [{shaderLocation: 0,offset: 0,format: "float32x2"} // position]},{arrayStride: 6 * 4, // 6 floats, 4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 1,offset: 0,format: "float32x4" // color},{shaderLocation: 2,offset: 16,format: "float32x2" // offset}]},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 3,offset: 0,format: "float32x2"} // scale]}]},fragment: {module: shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});const kNumObjects = 100;const staticUnitSize =4 * 4 + // color is 4 32bit floats (4bytes each)2 * 4; // offset is 2 32bit floats (4bytes each)const changingUnitSize = 2 * 4; // scale is 2 32 bit floatsconst staticVertexBufferSize = staticUnitSize * kNumObjects;const changingVertexBufferSize = changingUnitSize * kNumObjects;const staticVertexBuffer = device.createBuffer({label: "static storage for objects",size: staticVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const changingVertexBuffer = device.createBuffer({label: "changing storage for objects",size: changingVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const staticVertexValues = new Float32Array(staticVertexBufferSize / 4);const changingVertexValues = new Float32Array(changingVertexBufferSize / 4);const kColorOffset = 0;const kOffsetOffset = 4;const kScaleOffset = 0;const objectInfos: {scale: number;}[] = [];for (let i = 0; i < kNumObjects; ++i) {const staticOffset = i * (staticUnitSize / 4);staticVertexValues.set([rand(), rand(), rand(), 1],staticOffset + kColorOffset);staticVertexValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)],staticOffset + kOffsetOffset);objectInfos.push({scale: rand(0.2, 0.5)});}device.queue.writeBuffer(staticVertexBuffer, 0, staticVertexValues);const { vertexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25});const vertexBuffer = device.createBuffer({label: "vertex buffer vertices",size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});device.queue.writeBuffer(vertexBuffer, 0, vertexData);function frame() {const aspect = canvas.width / canvas.height;const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {label: "our basic canvas renderPass",colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0, vertexBuffer);objectInfos.forEach(({ scale }, ndx) => {const offset = ndx * (changingUnitSize / 4);changingVertexValues.set([scale / aspect, scale], offset + kScaleOffset); // set the scale});device.queue.writeBuffer(changingVertexBuffer, 0, changingVertexValues);renderPass.setVertexBuffer(1, staticVertexBuffer);renderPass.setVertexBuffer(2, changingVertexBuffer);renderPass.draw(numVertices, kNumObjects);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader.wgsl:


struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
};@vertex 
fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color;return vsOut;
}@fragment
fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

在这里插入图片描述

在这里插入图片描述

为了好玩,让我们添加第二个让我们为每个顶点的颜色添加另一个属性。首先改变着色器。

struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,@location(4) perVertexColor: vec3f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f,
};@vertex fn vs(vert: Vertex,
) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color * vec4f(vert.perVertexColor, 1);return vsOut;
}@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

然后我们需要更新管道以描述我们将如何提供数据。我们将像这样将perVertexColor数据与位置交织起来:

在这里插入图片描述

因此,需要更改arrayStride以覆盖我们的新数据,我们需要添加新属性。它从两个32位浮点数开始,所以它在缓冲区中的偏移量是8字节。

  const pipeline = device.createRenderPipeline({label: 'per vertex color',layout: 'auto',vertex: {module,entryPoint: 'vs',buffers: [{arrayStride: 5 * 4, // 5 floats, 4 bytes eachattributes: [{shaderLocation: 0, offset: 0, format: 'float32x2'},  // position{shaderLocation: 4, offset: 8, format: 'float32x3'},  // perVertexColor],},{arrayStride: 6 * 4, // 6 floats, 4 bytes eachstepMode: 'instance',attributes: [{shaderLocation: 1, offset:  0, format: 'float32x4'},  // color{shaderLocation: 2, offset: 16, format: 'float32x2'},  // offset],},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: 'instance',attributes: [{shaderLocation: 3, offset: 0, format: 'float32x2'},   // scale],},],},fragment: {module,entryPoint: 'fs',targets: [{ format: presentationFormat }],},});

我们将更新圆顶点的生成代码,为圆外缘的顶点提供深色,为内顶点提供浅色。

function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2,
} = {}) {// 2 triangles per subdivision, 3 verts per tri, 5 values (xyrgb) each.const numVertices = numSubdivisions * 3 * 2;const vertexData = new Float32Array(numVertices * (2 + 3) * 3 * 2);let offset = 0;const addVertex = (x, y, r, g, b) => {vertexData[offset++] = x;vertexData[offset++] = y;vertexData[offset++] = r;vertexData[offset++] = g;vertexData[offset++] = b;};const innerColor = [1, 1, 1];const outerColor = [0.1, 0.1, 0.1];// 2 vertices per subdivision//// 0--1 4// | / /|// |/ / |// 2 3--5for (let i = 0; i < numSubdivisions; ++i) {const angle1 = startAngle + (i + 0) * (endAngle - startAngle) / numSubdivisions;const angle2 = startAngle + (i + 1) * (endAngle - startAngle) / numSubdivisions;const c1 = Math.cos(angle1);const s1 = Math.sin(angle1);const c2 = Math.cos(angle2);const s2 = Math.sin(angle2);// first triangleaddVertex(c1 * radius, s1 * radius, ...outerColor);addVertex(c2 * radius, s2 * radius, ...outerColor);addVertex(c1 * innerRadius, s1 * innerRadius, ...innerColor);addVertex(c1 * innerRadius, s1 * innerRadius, ...innerColor);addVertex(c2 * radius, s2 * radius, ...outerColor);addVertex(c2 * innerRadius, s2 * innerRadius, ...innerColor);}return {vertexData,numVertices,};
}

这样我们就得到了阴影圈。

以下为完整代码及运行效果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-10-08 23:52:55* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="007vertex-srandom-circle3"><canvas id="gpucanvas"></canvas></div><script type="module" src="./007vertex-srandom-circle3.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-10-09 00:00:17* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";const rand = (min: undefined | number = undefined,max: undefined | number = undefined
) => {if (min === undefined) {min = 0;max = 1;} else if (max === undefined) {max = min;min = 0;}return min + Math.random() * (max - min);
};function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2
} = {}) {// 2 triangles per subdivision, 3 verts per tri, 5 values(xyrgb) eachconst numVertices = numSubdivisions * 3 * 2;const vertexData = new Float32Array(numSubdivisions * (2 + 3) * 3 * 2);let offset = 0;const addVertex = (x: number, y: number, r: number, g: number, b: number) => {vertexData[offset++] = x;vertexData[offset++] = y;vertexData[offset++] = r;vertexData[offset++] = g;vertexData[offset++] = b;};const innerColor = [1, 1, 1];const outerColor = [0.1, 0.1, 0.1];// 2 vertices per subdivision//// 0--1 4// | / /|// |/ / |// 2 3--5for (let i = 0; i < numSubdivisions; ++i) {const angle1 =startAngle + ((i + 0) * (endAngle - startAngle)) / numSubdivisions;const angle2 =startAngle + ((i + 1) * (endAngle - startAngle)) / numSubdivisions;const c1 = Math.cos(angle1);const s1 = Math.sin(angle1);const c2 = Math.cos(angle2);const s2 = Math.sin(angle2);// first angleaddVertex(c1 * radius,s1 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c2 * radius,s2 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c1 * innerRadius,s1 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);// second triangleaddVertex(c1 * innerRadius,s1 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);addVertex(c2 * radius,s2 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c2 * innerRadius,s2 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);}return {vertexData,numVertices};
}const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "per vertex color",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs",buffers: [{arrayStride: 5 * 4, // 5 floats, 4 bytes eachattributes: [{shaderLocation: 0,offset: 0,format: "float32x2"}, // position{shaderLocation: 4,offset: 8,format: "float32x3"} // position]},{arrayStride: 6 * 4, // 6 floats, 4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 1,offset: 0,format: "float32x4" // color},{shaderLocation: 2,offset: 16,format: "float32x2" // offset}]},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 3,offset: 0,format: "float32x2"} // scale]}]},fragment: {module: shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});const kNumObjects = 100;const staticUnitSize =4 * 4 + // color is 4 32bit floats (4bytes each)2 * 4; // offset is 2 32bit floats (4bytes each)const changingUnitSize = 2 * 4; // scale is 2 32 bit floatsconst staticVertexBufferSize = staticUnitSize * kNumObjects;const changingVertexBufferSize = changingUnitSize * kNumObjects;const staticVertexBuffer = device.createBuffer({label: "static vertex for objects",size: staticVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const changingVertexBuffer = device.createBuffer({label: "changing vertex for objects",size: changingVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const staticVertexValues = new Float32Array(staticVertexBufferSize / 4);const changingVertexValues = new Float32Array(changingVertexBufferSize / 4);const kColorOffset = 0;const kOffsetOffset = 4;const kScaleOffset = 0;const objectInfos: {scale: number;}[] = [];for (let i = 0; i < kNumObjects; ++i) {const staticOffset = i * (staticUnitSize / 4);staticVertexValues.set([rand(), rand(), rand(), 1],staticOffset + kColorOffset);staticVertexValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)],staticOffset + kOffsetOffset);objectInfos.push({scale: rand(0.2, 0.5)});}device.queue.writeBuffer(staticVertexBuffer, 0, staticVertexValues);const { vertexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25});const vertexBuffer = device.createBuffer({label: "vertex buffer vertices",size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});device.queue.writeBuffer(vertexBuffer, 0, vertexData);function frame() {const aspect = canvas.width / canvas.height;const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {label: "our basic canvas renderPass",colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0, vertexBuffer);objectInfos.forEach(({ scale }, ndx) => {const offset = ndx * (changingUnitSize / 4);changingVertexValues.set([scale / aspect, scale], offset + kScaleOffset); // set the scale});device.queue.writeBuffer(changingVertexBuffer, 0, changingVertexValues);renderPass.setVertexBuffer(1, staticVertexBuffer);renderPass.setVertexBuffer(2, changingVertexBuffer);renderPass.draw(numVertices, kNumObjects);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader.wgsl:


struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,@location(4) perVertexColor: vec3f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
};@vertex 
fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color * vec4f(vert.perVertexColor, 1);return vsOut;
}@fragment
fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

在这里插入图片描述

在这里插入图片描述

WGSL中的属性不必与JavaScript中的属性匹配

在上面的WGSL中,我们将perVertexColor属性声明为vec3f,如下所示

struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,@location(4) perVertexColor: vec3f,
};

像这样使用它

@vertex fn vs(vert: Vertex,
) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color * vec4f(vert.perVertexColor, 1);return vsOut;
}

我们也可以将它声明为vec4f,然后像这样使用它

struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,@location(4) perVertexColor: vec4f,
};...@vertex fn vs(vert: Vertex,
) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color * vert.perVertexColor;return vsOut;
}

不会改变任何其他东西。在JavaScript中,我们仍然只提供每个顶点3个浮点数的数据。

    {arrayStride: 5 * 4, // 5 floats, 4 bytes eachattributes: [{shaderLocation: 0, offset: 0, format: 'float32x2'},  // position{shaderLocation: 4, offset: 8, format: 'float32x3'},  // perVertexColor],},

这是可行的,因为在着色器中属性总是有4个值。它们默认为0,0,0,1,所以我们不提供的任何值都会得到默认值。

使用规范化的值来节省空间

我们使用32位浮点数表示颜色。每个perVertexColor有3个值,每个顶点每种颜色总共有12字节。每种颜色有4个值,每种颜色每个实例总共16字节。

我们可以优化一下,使用8位的值,并告诉WebGPU它们应该从0↔255到0.0↔1.0进行规范化

查看有效的属性格式列表,没有3值8bit格式,但有unorm8x4,所以让我们使用它。

首先,让我们更改生成顶点的代码,将颜色存储为8bit值并进行归一化

function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2,
} = {}) {// 2 triangles per subdivision, 3 verts per triconst numVertices = numSubdivisions * 3 * 2;// 2 32-bit values for position (xy) and 1 32-bit value for color (rgb_)// The 32-bit color value will be written/read as 4 8-bit valuesconst vertexData = new Float32Array(numVertices * (2 + 1));const colorData = new Uint8Array(vertexData.buffer);let offset = 0;let colorOffset = 8;const addVertex = (x, y, r, g, b) => {vertexData[offset++] = x;vertexData[offset++] = y;offset += 1;  // skip the colorcolorData[colorOffset++] = r * 255;colorData[colorOffset++] = g * 255;colorData[colorOffset++] = b * 255;colorOffset += 9;  // skip extra byte and the position};

上面我们创建了colorData,它是一个Uint8Array视图,与vertexData相同的数据

然后使用colorData来插入颜色,从0↔1扩展到0↔255

这些数据的内存布局如下所示

在这里插入图片描述

然后我们需要修改管道,将数据提取为8位无符号值,并将它们归一化回0↔1,更新偏移量,并将步长更新为新的大小。

  const pipeline = device.createRenderPipeline({label: 'per vertex color',layout: 'auto',vertex: {module,entryPoint: 'vs',buffers: [{arrayStride: 2 * 4 + 4, // 2 floats, 4 bytes each + 4 bytesattributes: [{shaderLocation: 0, offset: 0, format: 'float32x2'},  // position{shaderLocation: 4, offset: 8, format: 'unorm8x4'},   // perVertexColor],},{arrayStride: 4 + 2 * 4, // 4 bytes + 2 floats, 4 bytes eachstepMode: 'instance',attributes: [{shaderLocation: 1, offset: 0, format: 'unorm8x4'},   // color{shaderLocation: 2, offset: 4, format: 'float32x2'},  // offset],},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: 'instance',attributes: [{shaderLocation: 3, offset: 0, format: 'float32x2'},   // scale],},],},fragment: {module,entryPoint: 'fs',targets: [{ format: presentationFormat }],},});

这样就节省了一点空间。我们以前每个顶点使用20字节,现在我们使用12字节,节省了40%。我们在每个实例中使用24字节,现在我们使用12字节,节省了50%。

以下为完整代码及其运行效果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-10-09 21:00:14* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="007vertex-srandom-circle4"><canvas id="gpucanvas"></canvas></div><script type="module" src="./007vertex-srandom-circle4.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-10-09 21:13:36* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";const rand = (min: undefined | number = undefined,max: undefined | number = undefined
) => {if (min === undefined) {min = 0;max = 1;} else if (max === undefined) {max = min;min = 0;}return min + Math.random() * (max - min);
};function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2
} = {}) {// 2 triangles per subdivision, 3 verts per triconst numVertices = numSubdivisions * 3 * 2;// 2 32-bit values for position(xy) and 1 32-bit value for color(rgb_)// the 32-bit color value will be written/read as 4 8-bit valuesconst vertexData = new Float32Array(numVertices * (2 + 1));const colorData = new Uint8Array(vertexData.buffer);let offset = 0;let colorOffset = 8;const addVertex = (x: number, y: number, r: number, g: number, b: number) => {vertexData[offset++] = x;vertexData[offset++] = y;offset += 1; // skip the colorcolorData[colorOffset++] = r * 255;colorData[colorOffset++] = g * 255;colorData[colorOffset++] = b * 255;colorOffset += 9; // skip extra byte and the position};const innerColor = [1, 1, 1];const outerColor = [0.1, 0.1, 0.1];// 2 vertices per subdivision//// 0--1 4// | / /|// |/ / |// 2 3--5for (let i = 0; i < numSubdivisions; ++i) {const angle1 =startAngle + ((i + 0) * (endAngle - startAngle)) / numSubdivisions;const angle2 =startAngle + ((i + 1) * (endAngle - startAngle)) / numSubdivisions;const c1 = Math.cos(angle1);const s1 = Math.sin(angle1);const c2 = Math.cos(angle2);const s2 = Math.sin(angle2);// first angleaddVertex(c1 * radius,s1 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c2 * radius,s2 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c1 * innerRadius,s1 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);// second triangleaddVertex(c1 * innerRadius,s1 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);addVertex(c2 * radius,s2 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c2 * innerRadius,s2 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);}return {vertexData,numVertices};
}const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "per vertex color",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs",buffers: [{arrayStride: 2 * 4 + 4, // 2 floats, 4 bytes each + 4 bytesattributes: [{shaderLocation: 0,offset: 0,format: "float32x2"}, // position{shaderLocation: 4,offset: 8,format: "unorm8x4"} // position]},{arrayStride: 4 + 2 * 4, // 4 bytes + 2 floats,4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 1,offset: 0,format: "unorm8x4" // color},{shaderLocation: 2,offset: 4,format: "float32x2" // offset}]},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 3,offset: 0,format: "float32x2"} // scale]}]},fragment: {module: shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});const kNumObjects = 100;const staticUnitSize =4 + // color is 4 bytes2 * 4; // offset is 2 32bit floats (4bytes each)const changingUnitSize = 2 * 4; // scale is 2 32 bit floatsconst staticVertexBufferSize = staticUnitSize * kNumObjects;const changingVertexBufferSize = changingUnitSize * kNumObjects;const staticVertexBuffer = device.createBuffer({label: "static vertex for objects",size: staticVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const changingVertexBuffer = device.createBuffer({label: "changing vertex for objects",size: changingVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const staticVertexValuesU8 = new Uint8Array(staticVertexBufferSize);const staticVertexValuesF32 = new Float32Array(staticVertexValuesU8.buffer);const changingVertexValues = new Float32Array(changingVertexBufferSize / 4);const kColorOffset = 0;const kOffsetOffset = 1;const kScaleOffset = 0;const objectInfos: {scale: number;}[] = [];for (let i = 0; i < kNumObjects; ++i) {const staticOffsetU8 = i * staticUnitSize;const staticOffsetF32 = staticOffsetU8 / 4;staticVertexValuesU8.set([rand() * 255, rand() * 255, rand() * 255, 255],staticOffsetU8 + kColorOffset);staticVertexValuesF32.set([rand(-0.9, 0.9), rand(-0.9, 0.9)],staticOffsetF32 + kOffsetOffset);objectInfos.push({scale: rand(0.2, 0.5)});}device.queue.writeBuffer(staticVertexBuffer, 0, staticVertexValuesF32);const { vertexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25});const vertexBuffer = device.createBuffer({label: "vertex buffer vertices",size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});device.queue.writeBuffer(vertexBuffer, 0, vertexData);function frame() {const aspect = canvas.width / canvas.height;const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {label: "our basic canvas renderPass",colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0, vertexBuffer);objectInfos.forEach(({ scale }, ndx) => {const offset = ndx * (changingUnitSize / 4);changingVertexValues.set([scale / aspect, scale], offset + kScaleOffset); // set the scale});device.queue.writeBuffer(changingVertexBuffer, 0, changingVertexValues);renderPass.setVertexBuffer(1, staticVertexBuffer);renderPass.setVertexBuffer(2, changingVertexBuffer);renderPass.draw(numVertices, kNumObjects);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader.wgsl:


struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,@location(4) perVertexColor: vec3f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
};@vertex 
fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color * vec4f(vert.perVertexColor, 1);return vsOut;
}@fragment
fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

在这里插入图片描述

在这里插入图片描述

索引缓冲区

这里要介绍的最后一件事是索引缓冲区。索引缓冲区描述了处理和使用顶点的顺序。

你可以把绘制看成是按顺序遍历顶点

0, 1, 2, 3, 4, 5, .....

通过索引缓冲区,我们可以改变这个顺序。

我们为每个圆的细分创建了6个顶点,尽管其中2个是相同的。

在这里插入图片描述

现在,我们只创建4个顶点,然后通过告诉WebGPU按此顺序绘制索引,使用索引来使用这4个顶点6次

0, 1, 2, 2, 1, 3, ...

在这里插入图片描述

function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2,
} = {}) {// 2 vertices at each subdivision, + 1 to wrap around the circle.const numVertices = (numSubdivisions + 1) * 2;// 2 32-bit values for position (xy) and 1 32-bit value for color (rgb)// The 32-bit color value will be written/read as 4 8-bit valuesconst vertexData = new Float32Array(numVertices * (2 + 1));const colorData = new Uint8Array(vertexData.buffer);let offset = 0;let colorOffset = 8;const addVertex = (x, y, r, g, b) => {vertexData[offset++] = x;vertexData[offset++] = y;offset += 1;  // skip the colorcolorData[colorOffset++] = r * 255;colorData[colorOffset++] = g * 255;colorData[colorOffset++] = b * 255;colorOffset += 9;  // skip extra byte and the position};const innerColor = [1, 1, 1];const outerColor = [0.1, 0.1, 0.1];// 2 vertices per subdivision//// 0  2  4  6  8 ...//// 1  3  5  7  9 ...for (let i = 0; i <= numSubdivisions; ++i) {const angle = startAngle + (i + 0) * (endAngle - startAngle) / numSubdivisions;const c1 = Math.cos(angle);const s1 = Math.sin(angle);addVertex(c1 * radius, s1 * radius, ...outerColor);addVertex(c1 * innerRadius, s1 * innerRadius, ...innerColor);}const indexData = new Uint32Array(numSubdivisions * 6);let ndx = 0;// 0---2---4---...// | //| //|// |// |// |//// 1---3-- 5---...for (let i = 0; i < numSubdivisions; ++i) {const ndxOffset = i * 2;// first triangleindexData[ndx++] = ndxOffset;indexData[ndx++] = ndxOffset + 1;indexData[ndx++] = ndxOffset + 2;// second triangleindexData[ndx++] = ndxOffset + 2;indexData[ndx++] = ndxOffset + 1;indexData[ndx++] = ndxOffset + 3;}return {positionData,colorData,indexData,numVertices: indexData.length,};
}

然后我们需要创建一个索引缓冲区

  const { vertexData, indexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25,});const vertexBuffer = device.createBuffer({label: 'vertex buffer',size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,});device.queue.writeBuffer(vertexBuffer, 0, vertexData);const indexBuffer = device.createBuffer({label: 'index buffer',size: indexData.byteLength,usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,});device.queue.writeBuffer(indexBuffer, 0, indexData);

注意,我们将用法设置为INDEX。

最后,在绘制时,我们需要指定索引缓冲区

    pass.setPipeline(pipeline);pass.setVertexBuffer(0, vertexBuffer);pass.setVertexBuffer(1, staticVertexBuffer);pass.setVertexBuffer(2, changingVertexBuffer);pass.setIndexBuffer(indexBuffer, 'uint32');

因为我们的缓冲区包含32位无符号整数索引,我们需要在这里传递uint32。我们也可以使用16位无符号索引,在这种情况下,我们需要传入uint16

我们需要调用drawindex而不是draw

    pass.drawIndexed(numVertices, kNumObjects);

这样,我们节省了一些空间(33%),并且在顶点着色器中计算顶点时的潜在处理量类似,因为GPU可以重用它已经计算过的顶点。

以下为完整代码及运行结果:

HTML:

<!--* @Description: * @Author: tianyw* @Date: 2022-11-11 12:50:23* @LastEditTime: 2023-10-09 21:42:18* @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>001hello-triangle</title><style>html,body {margin: 0;width: 100%;height: 100%;background: #000;color: #fff;display: flex;text-align: center;flex-direction: column;justify-content: center;}div,canvas {height: 100%;width: 100%;}</style>
</head><body><div id="007vertex-srandom-circle5"><canvas id="gpucanvas"></canvas></div><script type="module" src="./007vertex-srandom-circle5.ts"></script></body></html>

TS:

/** @Description:* @Author: tianyw* @Date: 2023-04-08 20:03:35* @LastEditTime: 2023-10-09 21:53:07* @LastEditors: tianyw*/
export type SampleInit = (params: {canvas: HTMLCanvasElement;
}) => void | Promise<void>;import shaderWGSL from "./shaders/shader.wgsl?raw";const rand = (min: undefined | number = undefined,max: undefined | number = undefined
) => {if (min === undefined) {min = 0;max = 1;} else if (max === undefined) {max = min;min = 0;}return min + Math.random() * (max - min);
};function createCircleVertices({radius = 1,numSubdivisions = 24,innerRadius = 0,startAngle = 0,endAngle = Math.PI * 2
} = {}) {// 2 triangles per subdivision, 3 verts per triconst numVertices = (numSubdivisions + 1) * 2;// 2 32-bit values for position(xy) and 1 32-bit value for color(rgb_)// the 32-bit color value will be written/read as 4 8-bit valuesconst vertexData = new Float32Array(numVertices * (2 + 1));const colorData = new Uint8Array(vertexData.buffer);let offset = 0;let colorOffset = 8;const addVertex = (x: number, y: number, r: number, g: number, b: number) => {vertexData[offset++] = x;vertexData[offset++] = y;offset += 1; // skip the colorcolorData[colorOffset++] = r * 255;colorData[colorOffset++] = g * 255;colorData[colorOffset++] = b * 255;colorOffset += 9; // skip extra byte and the position};const innerColor = [1, 1, 1];const outerColor = [0.1, 0.1, 0.1];// 2 vertices per subdivision//// 0 2 4 6 8 ...// 1 3 5 7 9 ...for (let i = 0; i <= numSubdivisions; ++i) {const angle =startAngle + ((i + 0) * (endAngle - startAngle)) / numSubdivisions;const c1 = Math.cos(angle);const s1 = Math.sin(angle);addVertex(c1 * radius,s1 * radius,outerColor[0],outerColor[1],outerColor[2]);addVertex(c1 * innerRadius,s1 * innerRadius,innerColor[0],innerColor[1],innerColor[2]);}const indexData = new Uint32Array(numSubdivisions * 6);let ndx = 0;// 0---2---4---...// | //| //|// |// |// |//// 1---3-- 5---...for (let i = 0; i < numSubdivisions; ++i) {const ndxOffset = i * 2;// first triangleindexData[ndx++] = ndxOffset;indexData[ndx++] = ndxOffset + 1;indexData[ndx++] = ndxOffset + 2;// second triangleindexData[ndx++] = ndxOffset + 2;indexData[ndx++] = ndxOffset + 1;indexData[ndx++] = ndxOffset + 3;}return {vertexData,indexData,numVertices: indexData.length};
}const init: SampleInit = async ({ canvas }) => {const adapter = await navigator.gpu?.requestAdapter();if (!adapter) return;const device = await adapter?.requestDevice();if (!device) {console.error("need a browser that supports WebGPU");return;}const context = canvas.getContext("webgpu");if (!context) return;const devicePixelRatio = window.devicePixelRatio || 1;canvas.width = canvas.clientWidth * devicePixelRatio;canvas.height = canvas.clientHeight * devicePixelRatio;const presentationFormat = navigator.gpu.getPreferredCanvasFormat();context.configure({device,format: presentationFormat,alphaMode: "premultiplied"});const shaderModule = device.createShaderModule({label: "our hardcoded rgb triangle shaders",code: shaderWGSL});const renderPipeline = device.createRenderPipeline({label: "per vertex color",layout: "auto",vertex: {module: shaderModule,entryPoint: "vs",buffers: [{arrayStride: 2 * 4 + 4, // 2 floats, 4 bytes each + 4 bytesattributes: [{shaderLocation: 0,offset: 0,format: "float32x2"}, // position{shaderLocation: 4,offset: 8,format: "unorm8x4"} // position]},{arrayStride: 4 + 2 * 4, // 4 bytes + 2 floats,4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 1,offset: 0,format: "unorm8x4" // color},{shaderLocation: 2,offset: 4,format: "float32x2" // offset}]},{arrayStride: 2 * 4, // 2 floats, 4 bytes eachstepMode: "instance",attributes: [{shaderLocation: 3,offset: 0,format: "float32x2"} // scale]}]},fragment: {module: shaderModule,entryPoint: "fs",targets: [{format: presentationFormat}]},primitive: {// topology: "line-list"// topology: "line-strip"//  topology: "point-list"topology: "triangle-list"// topology: "triangle-strip"}});const kNumObjects = 100;const staticUnitSize =4 + // color is 4 bytes2 * 4; // offset is 2 32bit floats (4bytes each)const changingUnitSize = 2 * 4; // scale is 2 32 bit floatsconst staticVertexBufferSize = staticUnitSize * kNumObjects;const changingVertexBufferSize = changingUnitSize * kNumObjects;const staticVertexBuffer = device.createBuffer({label: "static vertex for objects",size: staticVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const changingVertexBuffer = device.createBuffer({label: "changing vertex for objects",size: changingVertexBufferSize,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});const staticVertexValuesU8 = new Uint8Array(staticVertexBufferSize);const staticVertexValuesF32 = new Float32Array(staticVertexValuesU8.buffer);const changingVertexValues = new Float32Array(changingVertexBufferSize / 4);const kColorOffset = 0;const kOffsetOffset = 1;const kScaleOffset = 0;const objectInfos: {scale: number;}[] = [];for (let i = 0; i < kNumObjects; ++i) {const staticOffsetU8 = i * staticUnitSize;const staticOffsetF32 = staticOffsetU8 / 4;staticVertexValuesU8.set([rand() * 255, rand() * 255, rand() * 255, 255],staticOffsetU8 + kColorOffset);staticVertexValuesF32.set([rand(-0.9, 0.9), rand(-0.9, 0.9)],staticOffsetF32 + kOffsetOffset);objectInfos.push({scale: rand(0.2, 0.5)});}device.queue.writeBuffer(staticVertexBuffer, 0, staticVertexValuesF32);const { vertexData, indexData, numVertices } = createCircleVertices({radius: 0.5,innerRadius: 0.25});const vertexBuffer = device.createBuffer({label: "vertex buffer vertices",size: vertexData.byteLength,usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST});device.queue.writeBuffer(vertexBuffer, 0, vertexData);const indexBuffer = device.createBuffer({label: "index buffer",size: indexData.byteLength,usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST});device.queue.writeBuffer(indexBuffer, 0, indexData);function frame() {const aspect = canvas.width / canvas.height;const renderCommandEncoder = device.createCommandEncoder({label: "render vert frag"});if (!context) return;const textureView = context.getCurrentTexture().createView();const renderPassDescriptor: GPURenderPassDescriptor = {label: "our basic canvas renderPass",colorAttachments: [{view: textureView,clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },loadOp: "clear",storeOp: "store"}]};const renderPass =renderCommandEncoder.beginRenderPass(renderPassDescriptor);renderPass.setPipeline(renderPipeline);renderPass.setVertexBuffer(0, vertexBuffer);objectInfos.forEach(({ scale }, ndx) => {const offset = ndx * (changingUnitSize / 4);changingVertexValues.set([scale / aspect, scale], offset + kScaleOffset); // set the scale});device.queue.writeBuffer(changingVertexBuffer, 0, changingVertexValues);renderPass.setVertexBuffer(1, staticVertexBuffer);renderPass.setVertexBuffer(2, changingVertexBuffer);renderPass.setIndexBuffer(indexBuffer, "uint32");renderPass.drawIndexed(numVertices, kNumObjects);renderPass.end();const renderBuffer = renderCommandEncoder.finish();device.queue.submit([renderBuffer]);requestAnimationFrame(frame);}requestAnimationFrame(frame);
};const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader.wgsl:


struct Vertex {@location(0) position: vec2f,@location(1) color: vec4f,@location(2) offset: vec2f,@location(3) scale: vec2f,@location(4) perVertexColor: vec3f,
};struct VSOutput {@builtin(position) position: vec4f,@location(0) color: vec4f
};@vertex 
fn vs(vert: Vertex) -> VSOutput {var vsOut: VSOutput;vsOut.position = vec4f(vert.position * vert.scale + vert.offset, 0.0, 1.0);vsOut.color = vert.color * vec4f(vert.perVertexColor, 1);return vsOut;
}@fragment
fn fs(vsOut: VSOutput) -> @location(0) vec4f {return vsOut.color;
}

在这里插入图片描述

在这里插入图片描述

请注意,我们还可以将索引缓冲区与上一篇文章中的存储缓冲区示例一起使用。在这种情况下,传入的@builtin(vertex_index)的值与索引缓冲区中的索引匹配。

接下来我们将介绍纹理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/109574.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot实战(二十五)集成 Shiro

目录 一、Shiro 简介1.1 Shiro 定义1.2 Shiro 核心组件1.3 Shiro 认证过程 二、SpringBoot集成2.1 集成思路2.2 Maven依赖2.3 自定义 Realm2.4 Shiro 配置类2.5 静态资源映射2.6 AuthController2.7 User 实体2.8 用户接口类2.9 用户接口实现类2.10 OrderController&#xff08;…

List小练习,实现添加图书,并且有序遍历

SuppressWarnings({"all"})public static void main(String[] args) {List list new LinkedList(); // List list new Vector(); // List list new ArrayList();list.add(new Book1("红楼小梦",35.5,"曹雪芹"));list.add(new B…

Mysql数据库 1. SQL基础语法和操作

一、Mysql逻辑结构 一个数据库软件可以包含许多数据库 一个数据库包含许多表 一个表中包含许多字段&#xff08;列&#xff09; 数据库软件——>数据库——>数据表——>字段&#xff08;列&#xff09;、元组&#xff08;行&#xff09; 二、SQL语言基础语法 1.SQL…

滚雪球学Java(53):从入门到精通:SimpleDateFormat类高深用法,让你的代码更简洁!

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

易点易动设备管理系统:提升生产企业设备保养效率的利器

在现代生产企业中&#xff0c;设备保养是确保生产线稳定运行和产品质量的关键环节。然而&#xff0c;传统的设备保养方式往往面临效率低下、数据不准确等问题&#xff0c;影响了生产效率和竞争力。随着科技的进步&#xff0c;易点易动设备管理系统应运而生&#xff0c;以其智能…

核酸管外观缺陷检测(一)

1.1 应用示例思路 (1) 对核酸管图像进行灰度化、阈值分割和连通域分析&#xff1b; (2) 筛选出待检测的区域&#xff0c;并对该区域进行变换校正&#xff1b; (3) 进一步获取待检测的ROI区域&#xff0c;并根据几何特征和阈值条件&#xff0c;来对核酸管外观进行检测&#x…

Windows10 Docker 安装教程

Docker Desktop是什么&#xff1f; Docker Desktop是适用于Windows的Docker桌面&#xff0c;是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序&#xff0c;为构建、交付和运行dockerized应用程序提供易于使用的开发环境。Docker Desktop for Windows 使用 …

Cron表达式每月20号晚18点执行

Cron表达式每月20号晚18点执行 0 0 18 20 * ?验证正确性

Java10年技术架构演进

一、前言 又快到了1024&#xff0c;现代人都喜欢以日期的特殊含义来纪念属于自己的节日。虽然有点牵强&#xff0c;但是做件事情&#xff0c;中国人总喜欢找个节日来纪念&#xff0c;程序员也是一样。甚至连1111被定义成光棍节&#xff0c;这也算再无聊不过了。不过作为程序员…

报考阿里云acp认证,你得到的是什么?

放眼全球能够和亚马逊AWS、微软Azure竞争的&#xff0c;国内也就只有阿里云了。 阿里云目前稳居国内云计算市场第一&#xff0c;比排后面5名同行市场占有率的总和还要多&#xff0c;全球云计算市场&#xff0c;阿里云目前排名第3位。 阿里云的市场占有率说明市场对于阿里云产…

kafka、zookeeper、flink测试环境、docker

1、kafka环境单点 根据官网版本说明(3.6.0)发布&#xff0c;zookeeper依旧在使用状态&#xff0c;预期在4.0.0大版本的时候彻底抛弃zookeeper使用KRaft(Apache Kafka)官方并给出了zk迁移KR的文档 2、使用docker启动单点kafka 1、首先将kafka启动命令&#xff0c;存储为.servi…

小程序:uniapp解决主包体积过大的问题

已经分包但还是体积过大 运行时勾选“运行时是否压缩代码”进行压缩 在manifest.json配置&#xff08;开启分包优化&#xff09; "mp-weixin" : {"optimization" : {"subPackages" : true}//.... },在app.json配置&#xff08;设置组件按需注入…

TCP通信-同时接受多个客户端消息

同时处理多个客户端消息的原理 代码实现 public class ClientDemo1 {public static void main(String[] args) {try {System.out.println("客户端启动");// 1、创建Socket通信管道请求有服务端的连接// public Socket(String host, int port)// 参数一&#xff1a;服…

现代 ERP 系统,如何使中小企业智能制造商受益?

中小企业智能制造商大多依靠手工操作或电子表格模式&#xff0c;或少数几个软件组成的集合体&#xff0c;或是依靠传统的ERP系统来管理企业运营。经营利润率低、订单到现金的周期缓慢、客户付款延迟、管理成本增加&#xff0c;使他们的生存变得更加困难。许多企业一直在以最少的…

视频模板SDK,为企业带来无限创意与效率

在当今的数字化时代&#xff0c;视频已经成为了信息传播的主流方式之一&#xff0c;对于企业来说&#xff0c;制作高质量的视频已经成为了一项重要的业务需求。然而&#xff0c;制作一部高质量的企业视频需要耗费大量时间和金钱&#xff0c;对于许多企业来说是一个不小的负担。…

软件测试基础知识整理(详细版)

一、软件测试概述 1、软件缺陷 软件缺陷&#xff1a;又称之为“Bug”。即计算机软件或程序中存在的某种破坏正常运行能力的问题、错误&#xff0c;或者隐藏的功能缺陷。 缺陷的表现形式&#xff1a; 软件没有实现产品规格说明书所要求的功能模块&#xff1b; 软件中出现了产…

数据结构 优先级队列(堆)

数据结构 优先级队列(堆) 文章目录 数据结构 优先级队列(堆)1. 优先级队列1.1 概念 2. 优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现…

再见Jenkins!你好,GitLink引擎,更强大的自动化部署工具!

文章目录 写在前面一、准备工作1.1 注册GitLink账号1.2 托管项目1.3 新建项目管理引擎流水线 二、开始构建流水线2.1 进入图形流水线编辑页2.2 添加git clone节点2.3 添加shell节点2.4 添加allure html节点2.5 添加新建GitLink疑修节点2.6 添加钉钉通知节点2.7 设置任务触发器2…

Python学习基础笔记七十五——Python调用其他程序

Python经常被用来开发自动化程序。自动化程序往往需要调用其他的程序。 例如&#xff0c;我们可以代码中调用wget程序&#xff0c;而不是自己开发下载的代码。 这就是我们经常做的&#xff0c;在我们的Python程序中调佣其它程序&#xff0c;帮我们实现功能。 Python中调用外部…

危险化工品出口注意事项及法规要求_箱讯科技

随着全球化工品市场的不断发展&#xff0c;危险化工品出口业务逐渐成为国际贸易的重要组成部分。然而&#xff0c;由于危险化工品具有潜在的危险性&#xff0c;出口过程中需严格遵守相关法规和注意事项&#xff0c;以确保运输安全和顺畅。本文将详细介绍危险化工品出口注意事项…