webgpu
, vulkan
, DX12
, metal
…https://javascriptwtf.com/
Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
rustup
(Instructions)wasm-pack
(Instructions)C++
glew
+ glfw
+ imgui
Rust
wgpu
+ winit
+ egui
First, create a rust project using cargo
Use your favorite editor and edit the src/main.rs
:
To run the code:
The result should be:
See the README.md for build instructions, or you can find the pre-build examples in the DEMO part on my GitHub page.
Let’s take a short break. Feel free to stretch, refill your coffee, and prepare for our next segment on integrating Rust with wgpu.
Have any questions so far?
glCommand
s depend on contextOpenGL
Vulkan
WebGPU
GPU
] in WebGPU)GPUCanvasContext
] in WebGPU)
GPUAdapter
] in WebGPU)GPUDevice
] in WebGPU)
Queue
].GPUQueue
] in WebGPU)
This is the vulkan || webgpu abstraction of rendering procedure.
Encoder & Renderpass object will return a CommandBuffer
, a complete sequence of commands that may be submitted to a command queue
let mut encoder = device.create_command_encoder(...);
{
// force out of scope since render_pass can't live longer than encoder
let mut render_pass = encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: ...,
depth_stencil_attachment: ...,
occlusion_query_set: None,
timestamp_writes: None,
});
render_pass.set_pipeline(...);
render_pass.set_vertex_buffer(...);
render_pass.set_index_buffer(...);
render_pass.set_bind_group(...);
render_pass.draw_indexed();
}
queue.submit(vec![encoder.finish()]);
let mut encoder = device.create_command_encoder(...);
{
// force out of scope since render_pass can't live longer than encoder
let mut render_pass = encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: ...,
depth_stencil_attachment: ...,
occlusion_query_set: None,
timestamp_writes: None,
});
render_pass.set_pipeline(...);
render_pass.set_vertex_buffer(...);
render_pass.set_index_buffer(...);
render_pass.set_bind_group(...);
render_pass.draw_indexed();
}
queue.submit(vec![encoder.finish()]);
let mut encoder = device.create_command_encoder(...);
{
// force out of scope since render_pass can't live longer than encoder
let mut render_pass = encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: ...,
depth_stencil_attachment: ...,
occlusion_query_set: None,
timestamp_writes: None,
});
render_pass.set_pipeline(...);
render_pass.set_vertex_buffer(...);
render_pass.set_index_buffer(...);
render_pass.set_bind_group(...);
render_pass.draw_indexed();
}
queue.submit(vec![encoder.finish()]);
RenderPipelineLayout
/// A `PipelineLayout` object describes the available binding groups of a pipeline.
/// It can be created with [`Device::create_pipeline_layout`].
RenderPipeline
/// A `RenderPipeline` object represents a graphics pipeline and its stages, bindings, vertex
/// buffers and targets. It can be created with [`Device::create_render_pipeline`].
Buffer
is GPU-accessible buffer 1
BufferSlice
: wgpu side buffer util used to set range of buffersBufferInitDescriptor
for initializationCreate a buffer and init with raw data.
let vertex_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
contents
: plain datausage
: buffer usageSet vertex buffer before the draw call in the render pass.
slot
: index in VertexState::buffers
BufferSlice
: wgpu rust side buffer slice
// in desc():
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<ModelVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x3,
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 2,
},
],
}
Data that is uniform in the same render pass
Comparing to OpenGL
:
bindgroup
OpenGL
: treats sampler*d
as a uniform
glUniform*
to set sampler in the shaderWGPU
: set bindgroup where TextureView
and Sampler
are specifies saperatelywgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
}
let diffuse_bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(
&diffuse_texture.sampler),
},
],
label: Some("diffuse_bind_group"),
});
BindGroupLayout
/// Handle to a binding group layout.
///
/// A `BindGroupLayout` is a handle to the GPU-side layout of a binding group. It can be used to
/// create a [`BindGroupDescriptor`] object, which in turn can be used to create a [`BindGroup`]
/// object with [`Device::create_bind_group`]. A series of `BindGroupLayout`s can also be used to
/// create a [`PipelineLayoutDescriptor`], which can be used to create a [`PipelineLayout`].
///
/// It can be created with [`Device::create_bind_group_layout`].
///
/// Corresponds to [WebGPU `GPUBindGroupLayout`](
/// https://gpuweb.github.io/gpuweb/#gpubindgrouplayout).
BindGroup
/// Handle to a binding group.
///
/// A `BindGroup` represents the set of resources bound to the bindings described by a
/// [`BindGroupLayout`]. It can be created with [`Device::create_bind_group`]. A `BindGroup` can
/// be bound to a particular [`RenderPass`] with [`RenderPass::set_bind_group`], or to a
/// [`ComputePass`] with [`ComputePass::set_bind_group`].
///
/// Corresponds to [WebGPU `GPUBindGroup`](https://gpuweb.github.io/gpuweb/#gpubindgroup).
struct CameraUniform {
view_proj: mat4x4<f32>,
};
@group(1) @binding(0)
var<uniform> camera: CameraUniform;
WGPU is excellent!
WGPU is excellent!
Except for WGSL… link
Take a look at ’the most creative language ever
Thanks to Naga
SPIR-V first class support
GLSL has many limitations (440+ & Vulkan semantics only)
HLSL/GLSL/MSL -> SPIRV -> WGSL -> SPIRV -> [platform native]
i32
, u32
, f32
, bool
@builtin(vertex_index)
, @builtin(position)
vec2f === vec2<f32>
vec3u === vec3<u32>
vec4i === vec4<i32>
mat2x3<f32> === mat2x3f
(row x column)array<f32,5>
, c must be const-declared.@group(0) @binding(0) var<storage> weights: array<f32>;
arrayLength()
builtin function to get the element count.struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vs_main(
@builtin(vertex_index) in_vertex_index: u32,
) -> VertexOutput {
var out: VertexOutput;
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5;
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(0.3, 0.2, 0.1, 1.0);
}
workgroupBarrier()
var<workgroup>
].// Create zero-initialized workgroup shared data
const workgroup_len : u32 = 8;
var<workgroup> workgroup_data: array<u32, workgroup_len>;
@group(0) @binding(0) var<storage, read> input_data: array<u32>;
@group(0) @binding(1) var<storage, read_write> output_data: u32;
// Our workgroup will execute workgroup_len invocations of the shader
@compute @workgroup_size(workgroup_len, 1, 1)
fn computeMain(@builtin(local_invocation_id) local_id: vec3<u32>) {
// Each invocation will populate the shared workgroup data from the input data
workgroup_data[local_id.x] = input_data[local_id.x];
// Wait for each invocation to populate their region of local data
workgroupBarrier();
// Get the sum of the elements in the array
// Input Data: [0, 1, 2, 3, 4, 5, 6, 7]
// Loop Pass 1: [1, 5, 9, 13, 4, 5, 6, 7]
// Loop Pass 2: [6, 22, 9, 13, 4, 5, 6, 7]
for (var current_size = workgroup_len / 2; current_size >= 1; current_size /= 2) {
var sum: u32 = 0;
if (local_id.x < current_size) {
// Read current values from workgroup_data
sum = workgroup_data[local_id.x * 2] + workgroup_data[local_id.x * 2 + 1];
}
// Wait until all invocations have finished reading from workgroup_data, and have calculated their respective sums
workgroupBarrier();
if (local_id.x < current_size) {
workgroup_data[local_id.x] = sum;
}
// Wait for each invocation to finish one iteration of the loop, and to have finished writing to workgroup_data
workgroupBarrier();
}
// Write the sum to the output
if (local_id.x == 0) {
output_data = workgroup_data[0];
}
}
@group(0) @binding(0) var<storage, read_write> v_indices: array<u32>;
@group(1) @binding(0) var texture: texture_storage_2d<rgba8unorm, write>;
// in fn main():
textureStore(texture, location, value);
var<storage, S>
S
denotes read-write access typeIf you have cloned the project, go to ./compute_example/
or download compute_example.zip