webgpu: Implement proper async pipeline creation and GPUPipelineError (#32636)

* Add GPUPipelineError

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Proper GetBindGroupLayout

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Proper Create*PipelineAsync

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Expectations

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* fixups

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* more good expectations

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
Samson 2024-08-08 13:48:43 +02:00 committed by GitHub
parent 08eb4faf4d
commit b8cf0cf9af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 465 additions and 790 deletions

View file

@ -14,15 +14,21 @@ use std::sync::{Arc, Mutex};
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSObject};
use webgpu::wgc::id::{BindGroupLayoutId, PipelineLayoutId};
use webgpu::wgc::pipeline::RenderPipelineDescriptor;
use webgpu::wgc::{
binding_model as wgpu_bind, command as wgpu_com, pipeline as wgpu_pipe, resource as wgpu_res,
};
use webgpu::{self, wgt, PopError, WebGPU, WebGPURequest, WebGPUResponse};
use webgpu::{
self, wgt, PopError, WebGPU, WebGPUComputePipeline, WebGPURenderPipeline, WebGPURequest,
WebGPUResponse,
};
use super::bindings::codegen::Bindings::WebGPUBinding::GPUPipelineErrorReason;
use super::bindings::codegen::UnionTypes::GPUPipelineLayoutOrGPUAutoLayoutMode;
use super::bindings::error::Fallible;
use super::gpu::AsyncWGPUListener;
use super::gpudevicelostinfo::GPUDeviceLostInfo;
use super::gpupipelineerror::GPUPipelineError;
use super::gpusupportedlimits::GPUSupportedLimits;
use super::types::GPUError;
use crate::dom::bindings::cell::DomRefCell;
@ -214,6 +220,118 @@ impl GPUDevice {
}
}
fn parse_render_pipeline(
&self,
descriptor: &GPURenderPipelineDescriptor,
) -> (
Option<(PipelineLayoutId, Vec<BindGroupLayoutId>)>,
RenderPipelineDescriptor<'static>,
) {
let (layout, implicit_ids, _) = self.get_pipeline_layout_data(&descriptor.parent.layout);
let desc = wgpu_pipe::RenderPipelineDescriptor {
label: convert_label(&descriptor.parent.parent),
layout,
cache: None,
vertex: wgpu_pipe::VertexState {
stage: wgpu_pipe::ProgrammableStageDescriptor {
module: descriptor.vertex.parent.module.id().0,
entry_point: Some(Cow::Owned(descriptor.vertex.parent.entryPoint.to_string())),
constants: Cow::Owned(HashMap::new()),
zero_initialize_workgroup_memory: true,
},
buffers: Cow::Owned(
descriptor
.vertex
.buffers
.iter()
.map(|buffer| wgpu_pipe::VertexBufferLayout {
array_stride: buffer.arrayStride,
step_mode: match buffer.stepMode {
GPUVertexStepMode::Vertex => wgt::VertexStepMode::Vertex,
GPUVertexStepMode::Instance => wgt::VertexStepMode::Instance,
},
attributes: Cow::Owned(
buffer
.attributes
.iter()
.map(|att| wgt::VertexAttribute {
format: convert_vertex_format(att.format),
offset: att.offset,
shader_location: att.shaderLocation,
})
.collect::<Vec<_>>(),
),
})
.collect::<Vec<_>>(),
),
},
fragment: descriptor
.fragment
.as_ref()
.map(|stage| wgpu_pipe::FragmentState {
stage: wgpu_pipe::ProgrammableStageDescriptor {
module: stage.parent.module.id().0,
entry_point: Some(Cow::Owned(stage.parent.entryPoint.to_string())),
constants: Cow::Owned(HashMap::new()),
zero_initialize_workgroup_memory: true,
},
targets: Cow::Owned(
stage
.targets
.iter()
.map(|state| {
Some(wgt::ColorTargetState {
format: convert_texture_format(state.format),
write_mask: wgt::ColorWrites::from_bits_retain(state.writeMask),
blend: state.blend.as_ref().map(|blend| wgt::BlendState {
color: convert_blend_component(&blend.color),
alpha: convert_blend_component(&blend.alpha),
}),
})
})
.collect::<Vec<_>>(),
),
}),
primitive: convert_primitive_state(&descriptor.primitive),
depth_stencil: descriptor.depthStencil.as_ref().map(|dss_desc| {
wgt::DepthStencilState {
format: convert_texture_format(dss_desc.format),
depth_write_enabled: dss_desc.depthWriteEnabled,
depth_compare: convert_compare_function(dss_desc.depthCompare),
stencil: wgt::StencilState {
front: wgt::StencilFaceState {
compare: convert_compare_function(dss_desc.stencilFront.compare),
fail_op: convert_stencil_op(dss_desc.stencilFront.failOp),
depth_fail_op: convert_stencil_op(dss_desc.stencilFront.depthFailOp),
pass_op: convert_stencil_op(dss_desc.stencilFront.passOp),
},
back: wgt::StencilFaceState {
compare: convert_compare_function(dss_desc.stencilBack.compare),
fail_op: convert_stencil_op(dss_desc.stencilBack.failOp),
depth_fail_op: convert_stencil_op(dss_desc.stencilBack.depthFailOp),
pass_op: convert_stencil_op(dss_desc.stencilBack.passOp),
},
read_mask: dss_desc.stencilReadMask,
write_mask: dss_desc.stencilWriteMask,
},
bias: wgt::DepthBiasState {
constant: dss_desc.depthBias,
slope_scale: *dss_desc.depthBiasSlopeScale,
clamp: *dss_desc.depthBiasClamp,
},
}
}),
multisample: wgt::MultisampleState {
count: descriptor.multisample.count,
mask: descriptor.multisample.mask as u64,
alpha_to_coverage_enabled: descriptor.multisample.alphaToCoverageEnabled,
},
multiview: None,
};
(implicit_ids, desc)
}
/// <https://gpuweb.github.io/gpuweb/#lose-the-device>
pub fn lose(&self, reason: GPUDeviceLostReason, msg: String) {
let lost_promise = &(*self.lost_promise.borrow());
@ -564,7 +682,7 @@ impl GPUDeviceMethods for GPUDevice {
.wgpu_id_hub()
.create_compute_pipeline_id(self.device.0.backend());
let (layout, implicit_ids, bgls) = self.get_pipeline_layout_data(&descriptor.parent.layout);
let (layout, implicit_ids, _) = self.get_pipeline_layout_data(&descriptor.parent.layout);
let desc = wgpu_pipe::ComputePipelineDescriptor {
label: convert_label(&descriptor.parent.parent),
@ -585,16 +703,15 @@ impl GPUDeviceMethods for GPUDevice {
compute_pipeline_id,
descriptor: desc,
implicit_ids,
async_sender: None,
})
.expect("Failed to create WebGPU ComputePipeline");
let compute_pipeline = webgpu::WebGPUComputePipeline(compute_pipeline_id);
GPUComputePipeline::new(
&self.global(),
self.channel.clone(),
compute_pipeline,
descriptor.parent.parent.label.clone().unwrap_or_default(),
bgls,
self,
)
}
@ -606,7 +723,36 @@ impl GPUDeviceMethods for GPUDevice {
comp: InRealm,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp);
promise.resolve_native(&self.CreateComputePipeline(descriptor));
let sender = response_async(&promise, self);
let compute_pipeline_id = self
.global()
.wgpu_id_hub()
.create_compute_pipeline_id(self.device.0.backend());
let (layout, implicit_ids, _) = self.get_pipeline_layout_data(&descriptor.parent.layout);
let desc = wgpu_pipe::ComputePipelineDescriptor {
label: convert_label(&descriptor.parent.parent),
layout,
stage: wgpu_pipe::ProgrammableStageDescriptor {
module: descriptor.compute.module.id().0,
entry_point: Some(Cow::Owned(descriptor.compute.entryPoint.to_string())),
constants: Cow::Owned(HashMap::new()),
zero_initialize_workgroup_memory: true,
},
cache: None,
};
self.channel
.0
.send(WebGPURequest::CreateComputePipeline {
device_id: self.device.0,
compute_pipeline_id,
descriptor: desc,
implicit_ids,
async_sender: Some(sender),
})
.expect("Failed to create WebGPU ComputePipeline");
promise
}
@ -747,129 +893,7 @@ impl GPUDeviceMethods for GPUDevice {
&self,
descriptor: &GPURenderPipelineDescriptor,
) -> DomRoot<GPURenderPipeline> {
let mut valid = true;
let (layout, implicit_ids, bgls) = self.get_pipeline_layout_data(&descriptor.parent.layout);
let desc = if valid {
Some(wgpu_pipe::RenderPipelineDescriptor {
label: convert_label(&descriptor.parent.parent),
layout,
cache: None,
vertex: wgpu_pipe::VertexState {
stage: wgpu_pipe::ProgrammableStageDescriptor {
module: descriptor.vertex.parent.module.id().0,
entry_point: Some(Cow::Owned(
descriptor.vertex.parent.entryPoint.to_string(),
)),
constants: Cow::Owned(HashMap::new()),
zero_initialize_workgroup_memory: true,
},
buffers: Cow::Owned(
descriptor
.vertex
.buffers
.iter()
.map(|buffer| wgpu_pipe::VertexBufferLayout {
array_stride: buffer.arrayStride,
step_mode: match buffer.stepMode {
GPUVertexStepMode::Vertex => wgt::VertexStepMode::Vertex,
GPUVertexStepMode::Instance => wgt::VertexStepMode::Instance,
},
attributes: Cow::Owned(
buffer
.attributes
.iter()
.map(|att| wgt::VertexAttribute {
format: convert_vertex_format(att.format),
offset: att.offset,
shader_location: att.shaderLocation,
})
.collect::<Vec<_>>(),
),
})
.collect::<Vec<_>>(),
),
},
fragment: descriptor
.fragment
.as_ref()
.map(|stage| wgpu_pipe::FragmentState {
stage: wgpu_pipe::ProgrammableStageDescriptor {
module: stage.parent.module.id().0,
entry_point: Some(Cow::Owned(stage.parent.entryPoint.to_string())),
constants: Cow::Owned(HashMap::new()),
zero_initialize_workgroup_memory: true,
},
targets: Cow::Owned(
stage
.targets
.iter()
.map(|state| {
Some(wgt::ColorTargetState {
format: convert_texture_format(state.format),
write_mask: match wgt::ColorWrites::from_bits(
state.writeMask,
) {
Some(mask) => mask,
None => {
valid = false;
wgt::ColorWrites::empty()
},
},
blend: state.blend.as_ref().map(|blend| wgt::BlendState {
color: convert_blend_component(&blend.color),
alpha: convert_blend_component(&blend.alpha),
}),
})
})
.collect::<Vec<_>>(),
),
}),
primitive: convert_primitive_state(&descriptor.primitive),
depth_stencil: descriptor.depthStencil.as_ref().map(|dss_desc| {
wgt::DepthStencilState {
format: convert_texture_format(dss_desc.format),
depth_write_enabled: dss_desc.depthWriteEnabled,
depth_compare: convert_compare_function(dss_desc.depthCompare),
stencil: wgt::StencilState {
front: wgt::StencilFaceState {
compare: convert_compare_function(dss_desc.stencilFront.compare),
fail_op: convert_stencil_op(dss_desc.stencilFront.failOp),
depth_fail_op: convert_stencil_op(
dss_desc.stencilFront.depthFailOp,
),
pass_op: convert_stencil_op(dss_desc.stencilFront.passOp),
},
back: wgt::StencilFaceState {
compare: convert_compare_function(dss_desc.stencilBack.compare),
fail_op: convert_stencil_op(dss_desc.stencilBack.failOp),
depth_fail_op: convert_stencil_op(dss_desc.stencilBack.depthFailOp),
pass_op: convert_stencil_op(dss_desc.stencilBack.passOp),
},
read_mask: dss_desc.stencilReadMask,
write_mask: dss_desc.stencilWriteMask,
},
bias: wgt::DepthBiasState {
constant: dss_desc.depthBias,
slope_scale: *dss_desc.depthBiasSlopeScale,
clamp: *dss_desc.depthBiasClamp,
},
}
}),
multisample: wgt::MultisampleState {
count: descriptor.multisample.count,
mask: descriptor.multisample.mask as u64,
alpha_to_coverage_enabled: descriptor.multisample.alphaToCoverageEnabled,
},
multiview: None,
})
} else {
self.dispatch_error(webgpu::Error::Validation(String::from(
"Invalid GPUColorWriteFlags",
)));
None
};
let (implicit_ids, desc) = self.parse_render_pipeline(&descriptor);
let render_pipeline_id = self
.global()
@ -883,6 +907,7 @@ impl GPUDeviceMethods for GPUDevice {
render_pipeline_id,
descriptor: desc,
implicit_ids,
async_sender: None,
})
.expect("Failed to create WebGPU render pipeline");
@ -892,7 +917,6 @@ impl GPUDeviceMethods for GPUDevice {
&self.global(),
render_pipeline,
descriptor.parent.parent.label.clone().unwrap_or_default(),
bgls,
self,
)
}
@ -904,7 +928,26 @@ impl GPUDeviceMethods for GPUDevice {
comp: InRealm,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp);
promise.resolve_native(&self.CreateRenderPipeline(descriptor));
let (implicit_ids, desc) = self.parse_render_pipeline(&descriptor);
let sender = response_async(&promise, self);
let render_pipeline_id = self
.global()
.wgpu_id_hub()
.create_render_pipeline_id(self.device.0.backend());
self.channel
.0
.send(WebGPURequest::CreateRenderPipeline {
device_id: self.device.0,
render_pipeline_id,
descriptor: desc,
implicit_ids,
async_sender: Some(sender),
})
.expect("Failed to create WebGPU render pipeline");
promise
}
@ -1010,6 +1053,48 @@ impl AsyncWGPUListener for GPUDevice {
promise.resolve_native(&error);
},
},
WebGPUResponse::ComputePipeline(result) => match result {
Ok(pipeline) => promise.resolve_native(&GPUComputePipeline::new(
&self.global(),
WebGPUComputePipeline(pipeline.id),
pipeline.label.into(),
self,
)),
Err(webgpu::Error::Validation(msg)) => {
promise.reject_native(&GPUPipelineError::new(
&self.global(),
msg.into(),
GPUPipelineErrorReason::Validation,
))
},
Err(webgpu::Error::OutOfMemory(msg) | webgpu::Error::Internal(msg)) => promise
.reject_native(&GPUPipelineError::new(
&self.global(),
msg.into(),
GPUPipelineErrorReason::Internal,
)),
},
WebGPUResponse::RenderPipeline(result) => match result {
Ok(pipeline) => promise.resolve_native(&GPURenderPipeline::new(
&self.global(),
WebGPURenderPipeline(pipeline.id),
pipeline.label.into(),
self,
)),
Err(webgpu::Error::Validation(msg)) => {
promise.reject_native(&GPUPipelineError::new(
&self.global(),
msg.into(),
GPUPipelineErrorReason::Validation,
))
},
Err(webgpu::Error::OutOfMemory(msg) | webgpu::Error::Internal(msg)) => promise
.reject_native(&GPUPipelineError::new(
&self.global(),
msg.into(),
GPUPipelineErrorReason::Internal,
)),
},
_ => unreachable!("Wrong response received on AsyncWGPUListener for GPUDevice"),
}
}