这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
318 changes: 318 additions & 0 deletions samples/hello-cube.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=1000">
<title>Web GPU Cube demo</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
</head>
<body>
<canvas height=1000 width=1000></canvas>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You set a 600px viewport above, so 1000x1000 would be mostly offscreen.

<script>
/*******
* This preview of Web GPU uses WHLSL as its shading language.
* The choice of shading language(s) that will be ingested by the API is still under deliberation.
*******/
const positionAttributeNum = 0;
const colorAttributeNum = 1;
const transformBindingNum = 0;
const bindGroupIndex = 0;

const shader = `
struct FragmentData {
float4 position : SV_Position;
float4 color : attribute(${colorAttributeNum});
}

vertex FragmentData vertex_main(
float4 position : attribute(${positionAttributeNum}),
float4 color : attribute(${colorAttributeNum}),
constant float4x4[] modelViewProjectionMatrix : register(b${transformBindingNum}))
{
FragmentData out;
out.position = mul(modelViewProjectionMatrix[0], position);
out.color = color;

return out;
}

fragment float4 fragment_main(float4 color : attribute(${colorAttributeNum})) : SV_Target 0
{
return color;
}
`;

let device, swapChain, verticesBuffer, bindGroupLayout, pipeline, renderPassDescriptor;
let projectionMatrix = mat4.create();
let mappedGroups = [];

const colorOffset = 4 * 4;
const vertexSize = 4 * 8;
const verticesArray = new Float32Array([
// float4 position, float4 color
1, -1, 1, 1, 1, 0, 1, 1,
-1, -1, 1, 1, 0, 0, 1, 1,
-1, -1, -1, 1, 0, 0, 0, 1,
1, -1, -1, 1, 1, 0, 0, 1,
1, -1, 1, 1, 1, 0, 1, 1,
-1, -1, -1, 1, 0, 0, 0, 1,

1, 1, 1, 1, 1, 1, 1, 1,
1, -1, 1, 1, 1, 0, 1, 1,
1, -1, -1, 1, 1, 0, 0, 1,
1, 1, -1, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, -1, -1, 1, 1, 0, 0, 1,

-1, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, -1, 1, 1, 1, 0, 1,
-1, 1, -1, 1, 0, 1, 0, 1,
-1, 1, 1, 1, 0, 1, 1, 1,
1, 1, -1, 1, 1, 1, 0, 1,

-1, -1, 1, 1, 0, 0, 1, 1,
-1, 1, 1, 1, 0, 1, 1, 1,
-1, 1, -1, 1, 0, 1, 0, 1,
-1, -1, -1, 1, 0, 0, 0, 1,
-1, -1, 1, 1, 0, 0, 1, 1,
-1, 1, -1, 1, 0, 1, 0, 1,

1, 1, 1, 1, 1, 1, 1, 1,
-1, 1, 1, 1, 0, 1, 1, 1,
-1, -1, 1, 1, 0, 0, 1, 1,
-1, -1, 1, 1, 0, 0, 1, 1,
1, -1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1,

1, -1, -1, 1, 1, 0, 0, 1,
-1, -1, -1, 1, 0, 0, 0, 1,
-1, 1, -1, 1, 0, 1, 0, 1,
1, 1, -1, 1, 1, 1, 0, 1,
1, -1, -1, 1, 1, 0, 0, 1,
-1, 1, -1, 1, 0, 1, 0, 1,
]);

async function init() {
const adapter = await navigator.gpu.requestAdapter();
device = await adapter.requestDevice();

const canvas = document.querySelector('canvas');
let canvasSize = canvas.getBoundingClientRect();
canvas.width = canvasSize.width;
canvas.height = canvasSize.height;

const aspect = Math.abs(canvas.width / canvas.height);
mat4.perspective(projectionMatrix, (2 * Math.PI) / 5, aspect, 1, 100.0);

const context = canvas.getContext('gpu');

const swapChainDescriptor = {
device: device,
format: "bgra8unorm"
};
swapChain = context.configureSwapChain(swapChainDescriptor);

const shaderModuleDescriptor = { code: shader };
const shaderModule = device.createShaderModule(shaderModuleDescriptor);

const verticesBufferDescriptor = { size: verticesArray.byteLength, usage: GPUBufferUsage.VERTEX };
let verticesArrayBuffer;
[verticesBuffer, verticesArrayBuffer] = device.createBufferMapped(verticesBufferDescriptor);

const verticesWriteArray = new Float32Array(verticesArrayBuffer);
verticesWriteArray.set(verticesArray);
verticesBuffer.unmap();

// Vertex Input
const positionAttributeDescriptor = {
shaderLocation: positionAttributeNum, // [[attribute(0)]]
offset: 0,
format: "float4"
};
const colorAttributeDescriptor = {
shaderLocation: colorAttributeNum,
offset: colorOffset,
format: "float4"
}
const vertexBufferDescriptor = {
attributeSet: [positionAttributeDescriptor, colorAttributeDescriptor],
stride: vertexSize,
stepMode: "vertex"
};
const vertexInputDescriptor = { vertexBuffers: [vertexBufferDescriptor] };

// Bind group binding layout
const transformBufferBindGroupLayoutBinding = {
binding: transformBindingNum, // id[[(0)]]
visibility: GPUShaderStageBit.VERTEX,
type: "uniform-buffer"
};

const bindGroupLayoutDescriptor = { bindings: [transformBufferBindGroupLayoutBinding] };
bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDescriptor);

// Pipeline
const depthStateDescriptor = {
depthWriteEnabled: true,
depthCompare: "less"
};

const pipelineLayoutDescriptor = { bindGroupLayouts: [bindGroupLayout] };
const pipelineLayout = device.createPipelineLayout(pipelineLayoutDescriptor);
const vertexStageDescriptor = {
module: shaderModule,
entryPoint: "vertex_main"
};
const fragmentStageDescriptor = {
module: shaderModule,
entryPoint: "fragment_main"
};
const colorState = {
format: "bgra8unorm",
alphaBlend: {
srcFactor: "src-alpha",
dstFactor: "one-minus-src-alpha",
operation: "add"
},
colorBlend: {
srcFactor: "src-alpha",
dstFactor: "one-minus-src-alpha",
operation: "add"
},
writeMask: GPUColorWriteBits.ALL
};
const pipelineDescriptor = {
layout: pipelineLayout,

vertexStage: vertexStageDescriptor,
fragmentStage: fragmentStageDescriptor,

primitiveTopology: "triangle-list",
colorStates: [colorState],
depthStencilState: depthStateDescriptor,
vertexInput: vertexInputDescriptor
};
pipeline = device.createRenderPipeline(pipelineDescriptor);

let colorAttachment = {
// attachment is acquired in render loop.
loadOp: "clear",
storeOp: "store",
clearColor: { r: 0.5, g: 1.0, b: 1.0, a: 1.0 } // GPUColor
};

// Depth stencil texture

// GPUExtent3D
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we could do something more type-safe here. It seems silly to attach a 3D size to a 2D texture.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe we need GPUSize2D and GPUSize3D (I prefer both of them over "Extent")

const depthSize = {
width: canvas.width,
height: canvas.height,
depth: 1
};

const depthTextureDescriptor = {
size: depthSize,
arrayLayerCount: 1,
mipLevelCount: 1,
sampleCount: 1,
dimension: "2d",
format: "depth32float-stencil8",
usage: GPUTextureUsage.OUTPUT_ATTACHMENT
};

const depthTexture = device.createTexture(depthTextureDescriptor);

// GPURenderPassDepthStencilAttachmentDescriptor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment.

const depthAttachment = {
attachment: depthTexture.createDefaultView(),
depthLoadOp: "clear",
depthStoreOp: "store",
clearDepth: 1.0
};

renderPassDescriptor = {
colorAttachments: [colorAttachment],
depthStencilAttachment: depthAttachment
};

render();
}

// Transform Buffers and Bindings

const transformSize = 4 * 16;
const transformBufferDescriptor = {
size: transformSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.MAP_WRITE
};

function render() {
if (mappedGroups.length === 0) {
const [buffer, arrayBuffer] = device.createBufferMapped(transformBufferDescriptor);
const group = device.createBindGroup(createBindGroupDescriptor(buffer));
let mappedGroup = { buffer: buffer, arrayBuffer: arrayBuffer, bindGroup: group };
drawCommands(mappedGroup);
} else
drawCommands(mappedGroups.shift());
}

function createBindGroupDescriptor(transformBuffer) {
const transformBufferBinding = {
buffer: transformBuffer,
offset: 0,
size: transformSize
};
const transformBufferBindGroupBinding = {
binding: transformBindingNum,
resource: transformBufferBinding
};
return {
layout: bindGroupLayout,
bindings: [transformBufferBindGroupBinding]
};
}

function drawCommands(mappedGroup) {
updateTransformArray(new Float32Array(mappedGroup.arrayBuffer));
mappedGroup.buffer.unmap();

const commandEncoder = device.createCommandEncoder();
renderPassDescriptor.colorAttachments[0].attachment = swapChain.getCurrentTexture().createDefaultView();
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);

// Encode drawing commands

passEncoder.setPipeline(pipeline);
// Vertex attributes
passEncoder.setVertexBuffers(0, [verticesBuffer], [0]);
// Bind groups
passEncoder.setBindGroup(bindGroupIndex, mappedGroup.bindGroup);
// 36 vertices, 1 instance, 0th vertex, 0th instance.
passEncoder.draw(36, 1, 0, 0);
passEncoder.endPass();

device.getQueue().submit([commandEncoder.finish()]);

// Ready the current buffer for update after GPU is done with it.
mappedGroup.buffer.mapWriteAsync().then((arrayBuffer) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The right thing will happen here, but we might not wants apps to rely on the specific timeline that the mapWriteAsync promise resolves. It may be better to explicitly handle the dependency of "the mapping should happen after the command buffer finishes" by using fences. I'm not sure which behavior we want to promote.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this does the right thing, it's probably more readable than barriers.

The thing that I think is a bit weird is that mappedGroups could grow infinitely (if some other part of the code appends to it outside this loop). It's a shame we can't attach the group to the queue or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this example working with my mapWriteAsync() prototype. I could use a class to control access to the mappedGroups queue, if that's what you mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I understand what you're saying now. Shouldn't mapWriteAsync only resolve after the GPU is done with the buffer?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it means that we don't have any pipelining here based on how I understand it. We cannot upload the data for the second frame until the GPU is done with the data for the first. Our example should support double-buffering.

mappedGroup.arrayBuffer = arrayBuffer;
mappedGroups.push(mappedGroup);
});

requestAnimationFrame(render);
}

function updateTransformArray(array) {
let viewMatrix = mat4.create();
mat4.translate(viewMatrix, viewMatrix, vec3.fromValues(0, 0, -5));
let now = Date.now() / 1000;
mat4.rotate(viewMatrix, viewMatrix, 1, vec3.fromValues(Math.sin(now), Math.cos(now), 0));
let modelViewProjectionMatrix = mat4.create();
mat4.multiply(modelViewProjectionMatrix, projectionMatrix, viewMatrix);
mat4.copy(array, modelViewProjectionMatrix);
}

window.addEventListener("load", init);
</script>
</body>
</html>