Rendering
To render primitives, it's essential to first establish vertex inputs, Shaders, Shader-associated binding groups, and rendering states before initiating a render call.
Vertex inputs
Vertex inputs consist of a set of vertex buffers (index buffer).
// Creates vertex buffers
const vbPos = device.createVertexBuffer('position_f32x3', new Float32Array(vertices));
const vbTexCoord = device.createVertexBuffer('tex0_f32x2', new Float32Array(normals));
const ib = device.createIndexBuffer(new Uint16Array(indices));
// Vertex input layout
const vertexLayout = device.createVertexLayout({
// Vertex buffers
vertexBuffers: [{
// Vertex buffer object
buffer: vbPos,
// Vertex buffer step mode, can be 'vertex' or 'instance', default is 'vertex'
stepMode: 'vertex'
}, {
buffer: vbTexCoord,
stepMode: 'vertex'
}],
// Index buffer if needed
indexBuffer: ib
});
// Set as current vertex input layout
device.setVertexLayout(vertexLayout);Shader and bind groups
When rendering, it's necessary to set the current Shader. For more information, see:Shader
// Create shader
const program = device.buildRenderProgram({
vertex(pb){
// VertexShader
},
fragment(pb){
// Fragment
}
});
// Set as current shader
device.setProgram(program);Along with setting the current Shader, it's also required to configure the current binding group to supply constants to the Shader.
// Creates binding groups
const bindGroup0 = device.createBindGroup(program.bindGroupLayouts[0]);
const bindGroup1 = device.createBindGroup(program.bindGroupLayouts[1]);
// Set uniform values
bindGroup0.setValue('a', VALUE);
bindGroup1.setTexture('t', TEXTURE);
// Set current shader and binding groups before rendering
device.setProgram(program);
device.setBindGroup(0, bindGroup0);
device.setBindGroup(1, bindGroup1);Rendering states
Before rendering, setting the current rendering state might be needed. For more details, refer to Rendering states
// Creates rendering states object
const renderStates = device.createRenderStateSet();
renderStates.useDepthState().enableTest(false).enableWrite(false);
// Set as current rendering states object before rendering
device.setRenderStates(renderStates);Viewport and Scissor
Setting the viewport and scissor rectangle may be required before rendering.
In native WebGPU, the viewport's origin is at the top-left corner of the screen. However, to standardize the viewport origin between WebGL and WebGPU devices, we've adjusted it to be at the bottom-left corner of the screen.
// Set viewport
device.setViewport([X, Y, WIDTH, HEIGHT]);
// Set scissor
device.setScissor([X, Y, WIDTH, HEIGHT]);
// Sets the size of the viewport to match the current framebuffer (if the framebuffer size changes, the viewport size automatically follows)
device.setViewport(null);
// Set the scissor rectangle to match the size of the current framebuffer (if the framebuffer size changes, the scissor rectangle size will automatically follow)
device.setScissor(null);
// Get current viewport
const viewport = device.getViewport();
// Get current scissor rectangle
const scissor = device.getScissor();Draw call
Once all the necessary states are set, we can initiate the rendering call.
// Clear frame buffer
// parameter 1: RGBA for clearing color buffers,if null, color buffers will not been cleared.
// parameter 2: Clear value of depth buffer,if null, depth buffer will not be cleared
// parameter 3: Clear value of stencil buffer,if null, stencil buffer will no be cleared
device.clearFrameBuffer(new Vector4(0, 0, 1, 1), 1, 0);
// Draw triangles
// parameter 1:vertex topologic, possible values: 'triangle-list','triangle-strip','triangle-fan','line-list','line-strip','point-list'
// parameter 2: Start index
// parameter 3: Number of indices
device.draw('triangle-list', 0, 100);
// Draw instances of triangles
// parameter 1:vertex topologic, possible values: 'triangle-list','triangle-strip','triangle-fan','line-list','line-strip','point-list'
// parameter 2: Start index
// parameter 3: Number of indices
// parameter 4: How many instances should be rendered
device.drawInstanced('triangle-list', 0, 100, 20);
// The device provides a simple text rendering function with no typesetting function
// Sets the current text rendering font, the default value is '12px arial'
device.setFont('16px arial');
// Rendering text
// parameter 1: text content
// parameter 2:pixel offset related to the left of screen
// parameter 3: pixel offset related to the top of screen
// parameter 4: color of the text (CSS value)
device.drawText('Hello world!', 100, 100, '#ffffff');Frame buffer
The Framebuffer is our rendering destination, which can be the screen or a custom Framebuffer we define.
// Create a 2D texture to use as a color buffer
const colorTex = device.createTexture2D('rgba8unorm', 256, 256);
// Create a 2D depth texture to use as a depth buffer
const depthTex = device.createTexture2D('d24s8', 256, 256);
// Creates frame buffer
// parameter 1: A list of color buffer textures
// parameter 2: Depth buffer texture, which can be null
const framebuffer = device.createFrameBuffer([colorTex], depthTex);
// Set as current frame buffer
device.setFramebuffer(framebuffer);
// Set screen as current frame buffer
device.setFramebuffer(null);
// Get current frame buffer
const framebuffer = device.getFramebuffer();Changing the current Framebuffer will reset the current viewport and scissor rectangle.
Save states
// This method saves all current render states, including:
// current shader
// current binding groups
// current vertex input layout
// current frame buffer
// current viewport
// current scissor rectangle
// current rendering states
device.pushDeviceStates();
// Restore the last saved render state
device.popDeviceStates();GPU Compute
For GPU computations, it's only required to set up the compute Shader and the related binding groups, then call Device.compute(), which is available only on WebGPU devices.
// Set compute shader and binding groups
device.setProgram(computeProgram);
device.setBindGroup(0, bindGroup);
// Dispatches the compute task
// parameter 0:Thread group X-dimension length
// parameter 1:Thread group Y-dimension length
// parameter 2:Thread group Z-dimension length
device.compute(8, 1, 1);