diff options
Diffstat (limited to 'components/webgpu')
-rw-r--r-- | components/webgpu/dom_messages.rs | 23 | ||||
-rw-r--r-- | components/webgpu/identity.rs | 9 | ||||
-rw-r--r-- | components/webgpu/lib.rs | 2 | ||||
-rw-r--r-- | components/webgpu/render_commands.rs | 151 | ||||
-rw-r--r-- | components/webgpu/script_messages.rs | 6 | ||||
-rw-r--r-- | components/webgpu/wgpu_thread.rs | 172 |
6 files changed, 332 insertions, 31 deletions
diff --git a/components/webgpu/dom_messages.rs b/components/webgpu/dom_messages.rs index ec407a77858..a79aeea4b3d 100644 --- a/components/webgpu/dom_messages.rs +++ b/components/webgpu/dom_messages.rs @@ -16,7 +16,7 @@ use wgc::binding_model::{ BindGroupDescriptor, BindGroupLayoutDescriptor, PipelineLayoutDescriptor, }; use wgc::command::{ - ImageCopyBuffer, ImageCopyTexture, RenderBundleDescriptor, RenderBundleEncoder, RenderPass, + ImageCopyBuffer, ImageCopyTexture, RenderBundleDescriptor, RenderBundleEncoder, }; use wgc::device::HostMap; use wgc::id; @@ -25,10 +25,12 @@ use wgc::pipeline::{ComputePipelineDescriptor, RenderPipelineDescriptor}; use wgc::resource::{ BufferDescriptor, SamplerDescriptor, TextureDescriptor, TextureViewDescriptor, }; +use wgpu_core::command::{RenderPassColorAttachment, RenderPassDepthStencilAttachment}; use wgpu_core::pipeline::CreateShaderModuleError; pub use {wgpu_core as wgc, wgpu_types as wgt}; use crate::identity::*; +use crate::render_commands::RenderCommand; use crate::{Error, ErrorFilter, PopError, WebGPU, PRESENTATION_BUFFER_COUNT}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] @@ -236,6 +238,7 @@ pub enum WebGPURequest { DropRenderBundle(id::RenderBundleId), DropQuerySet(id::QuerySetId), DropComputePass(id::ComputePassEncoderId), + DropRenderPass(id::RenderPassEncoderId), Exit(IpcSender<()>), RenderBundleEncoderFinish { render_bundle_encoder: RenderBundleEncoder, @@ -255,6 +258,7 @@ pub enum WebGPURequest { device_id: id::DeviceId, pipeline_id: PipelineId, }, + // Compute Pass BeginComputePass { command_encoder_id: id::CommandEncoderId, compute_pass_id: ComputePassId, @@ -291,9 +295,24 @@ pub enum WebGPURequest { device_id: id::DeviceId, command_encoder_id: id::CommandEncoderId, }, + // Render Pass + BeginRenderPass { + command_encoder_id: id::CommandEncoderId, + render_pass_id: RenderPassId, + label: Option<Cow<'static, str>>, + color_attachments: Vec<Option<RenderPassColorAttachment>>, + depth_stencil_attachment: Option<RenderPassDepthStencilAttachment>, + device_id: id::DeviceId, + }, + RenderPassCommand { + render_pass_id: RenderPassId, + render_command: RenderCommand, + device_id: id::DeviceId, + }, EndRenderPass { - render_pass: Option<RenderPass>, + render_pass_id: RenderPassId, device_id: id::DeviceId, + command_encoder_id: id::CommandEncoderId, }, Submit { queue_id: id::QueueId, diff --git a/components/webgpu/identity.rs b/components/webgpu/identity.rs index bb1cb1ca4a0..2203f6db35a 100644 --- a/components/webgpu/identity.rs +++ b/components/webgpu/identity.rs @@ -5,13 +5,17 @@ use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use serde::{Deserialize, Serialize}; -pub use crate::wgc::id::markers::ComputePassEncoder as ComputePass; -pub use crate::wgc::id::ComputePassEncoderId as ComputePassId; +pub use crate::wgc::id::markers::{ + ComputePassEncoder as ComputePass, RenderPassEncoder as RenderPass, +}; use crate::wgc::id::{ AdapterId, BindGroupId, BindGroupLayoutId, BufferId, CommandBufferId, CommandEncoderId, ComputePipelineId, DeviceId, PipelineLayoutId, QueueId, RenderBundleId, RenderPipelineId, SamplerId, ShaderModuleId, SurfaceId, TextureId, TextureViewId, }; +pub use crate::wgc::id::{ + ComputePassEncoderId as ComputePassId, RenderPassEncoderId as RenderPassId, +}; macro_rules! webgpu_resource { ($name:ident, $id:ty) => { @@ -46,3 +50,4 @@ webgpu_resource!(WebGPUSurface, SurfaceId); webgpu_resource!(WebGPUTexture, TextureId); webgpu_resource!(WebGPUTextureView, TextureViewId); webgpu_resource!(WebGPUComputePass, ComputePassId); +webgpu_resource!(WebGPURenderPass, RenderPassId); diff --git a/components/webgpu/lib.rs b/components/webgpu/lib.rs index d3fc3068b97..cb2e4f7ebe5 100644 --- a/components/webgpu/lib.rs +++ b/components/webgpu/lib.rs @@ -19,6 +19,7 @@ use arrayvec::ArrayVec; use euclid::default::Size2D; pub use gpu_error::{Error, ErrorFilter, PopError}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +pub use render_commands::RenderCommand; use serde::{Deserialize, Serialize}; use servo_config::pref; use webrender_api::{DocumentId, ImageData, ImageDescriptor, ImageKey}; @@ -29,6 +30,7 @@ use wgc::id; mod dom_messages; mod gpu_error; +mod render_commands; mod script_messages; pub use dom_messages::*; pub use identity::*; diff --git a/components/webgpu/render_commands.rs b/components/webgpu/render_commands.rs new file mode 100644 index 00000000000..b66c9d616c6 --- /dev/null +++ b/components/webgpu/render_commands.rs @@ -0,0 +1,151 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Render pass commands + +use serde::{Deserialize, Serialize}; +use wgpu_core::command::{DynRenderPass, RenderPassError}; +use wgpu_core::global::Global; + +use crate::wgc::id; +use crate::wgt; + +/// <https://github.com/gfx-rs/wgpu/blob/f25e07b984ab391628d9568296d5970981d79d8b/wgpu-core/src/command/render_command.rs#L17> +#[derive(Debug, Deserialize, Serialize)] +pub enum RenderCommand { + SetPipeline(id::RenderPipelineId), + SetBindGroup { + index: u32, + bind_group_id: id::BindGroupId, + offsets: Vec<u32>, + }, + SetViewport { + x: f32, + y: f32, + width: f32, + height: f32, + min_depth: f32, + max_depth: f32, + }, + SetScissorRect { + x: u32, + y: u32, + width: u32, + height: u32, + }, + SetBlendConstant(wgt::Color), + SetStencilReference(u32), + SetIndexBuffer { + buffer_id: id::BufferId, + index_format: wgt::IndexFormat, + offset: u64, + size: Option<wgt::BufferSize>, + }, + SetVertexBuffer { + slot: u32, + buffer_id: id::BufferId, + offset: u64, + size: Option<wgt::BufferSize>, + }, + Draw { + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + }, + DrawIndexed { + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, + }, + DrawIndirect { + buffer_id: id::BufferId, + offset: u64, + }, + DrawIndexedIndirect { + buffer_id: id::BufferId, + offset: u64, + }, + ExecuteBundles(Vec<id::RenderBundleId>), +} + +pub fn apply_render_command( + context: &Global, + pass: &mut Box<dyn DynRenderPass>, + command: RenderCommand, +) -> Result<(), RenderPassError> { + match command { + RenderCommand::SetPipeline(pipeline_id) => pass.set_pipeline(context, pipeline_id), + RenderCommand::SetBindGroup { + index, + bind_group_id, + offsets, + } => pass.set_bind_group(context, index, bind_group_id, &offsets), + RenderCommand::SetViewport { + x, + y, + width, + height, + min_depth, + max_depth, + } => pass.set_viewport(context, x, y, width, height, min_depth, max_depth), + RenderCommand::SetScissorRect { + x, + y, + width, + height, + } => pass.set_scissor_rect(context, x, y, width, height), + RenderCommand::SetBlendConstant(color) => pass.set_blend_constant(context, color), + RenderCommand::SetStencilReference(reference) => { + pass.set_stencil_reference(context, reference) + }, + RenderCommand::SetIndexBuffer { + buffer_id, + index_format, + offset, + size, + } => pass.set_index_buffer(context, buffer_id, index_format, offset, size), + RenderCommand::SetVertexBuffer { + slot, + buffer_id, + offset, + size, + } => pass.set_vertex_buffer(context, slot, buffer_id, offset, size), + RenderCommand::Draw { + vertex_count, + instance_count, + first_vertex, + first_instance, + } => pass.draw( + context, + vertex_count, + instance_count, + first_vertex, + first_instance, + ), + RenderCommand::DrawIndexed { + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + } => pass.draw_indexed( + context, + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + ), + RenderCommand::DrawIndirect { buffer_id, offset } => { + pass.draw_indirect(context, buffer_id, offset) + }, + RenderCommand::DrawIndexedIndirect { buffer_id, offset } => { + pass.draw_indexed_indirect(context, buffer_id, offset) + }, + RenderCommand::ExecuteBundles(bundles) => pass.execute_bundles(context, &bundles), + } +} diff --git a/components/webgpu/script_messages.rs b/components/webgpu/script_messages.rs index 774bfd6cb7c..35571bb9242 100644 --- a/components/webgpu/script_messages.rs +++ b/components/webgpu/script_messages.rs @@ -11,8 +11,9 @@ use crate::gpu_error::Error; use crate::identity::WebGPUDevice; use crate::wgc::id::{ AdapterId, BindGroupId, BindGroupLayoutId, BufferId, CommandBufferId, ComputePassEncoderId, - ComputePipelineId, DeviceId, PipelineLayoutId, QuerySetId, RenderBundleId, RenderPipelineId, - SamplerId, ShaderModuleId, StagingBufferId, SurfaceId, TextureId, TextureViewId, + ComputePipelineId, DeviceId, PipelineLayoutId, QuerySetId, RenderBundleId, RenderPassEncoderId, + RenderPipelineId, SamplerId, ShaderModuleId, StagingBufferId, SurfaceId, TextureId, + TextureViewId, }; /// <https://gpuweb.github.io/gpuweb/#enumdef-gpudevicelostreason> @@ -45,6 +46,7 @@ pub enum WebGPUMsg { FreeStagingBuffer(StagingBufferId), FreeQuerySet(QuerySetId), FreeComputePass(ComputePassEncoderId), + FreeRenderPass(RenderPassEncoderId), UncapturedError { device: WebGPUDevice, pipeline_id: PipelineId, diff --git a/components/webgpu/wgpu_thread.rs b/components/webgpu/wgpu_thread.rs index 4e77813551b..04ff5b2a25a 100644 --- a/components/webgpu/wgpu_thread.rs +++ b/components/webgpu/wgpu_thread.rs @@ -18,7 +18,9 @@ use servo_config::pref; use webrender::{RenderApi, RenderApiSender, Transaction}; use webrender_api::{DirtyRect, DocumentId}; use webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType}; -use wgc::command::{ImageCopyBuffer, ImageCopyTexture}; +use wgc::command::{ + ComputePassDescriptor, DynComputePass, DynRenderPass, ImageCopyBuffer, ImageCopyTexture, +}; use wgc::device::queue::SubmittedWorkDoneClosure; use wgc::device::{DeviceDescriptor, DeviceLostClosure, HostMap, ImplicitPipelineIds}; use wgc::id::DeviceId; @@ -26,15 +28,16 @@ use wgc::instance::parse_backends_from_comma_list; use wgc::pipeline::ShaderModuleDescriptor; use wgc::resource::{BufferMapCallback, BufferMapOperation}; use wgc::{gfx_select, id}; -use wgpu_core::command::{ComputePassDescriptor, DynComputePass}; +use wgpu_core::command::RenderPassDescriptor; use wgt::InstanceDescriptor; pub use {wgpu_core as wgc, wgpu_types as wgt}; use crate::gpu_error::ErrorScope; use crate::poll_thread::Poller; +use crate::render_commands::apply_render_command; use crate::{ - ComputePassId, Error, PopError, PresentationData, Transmute, WebGPU, WebGPUAdapter, - WebGPUDevice, WebGPUMsg, WebGPUQueue, WebGPURequest, WebGPUResponse, + ComputePassId, Error, PopError, PresentationData, RenderPassId, Transmute, WebGPU, + WebGPUAdapter, WebGPUDevice, WebGPUMsg, WebGPUQueue, WebGPURequest, WebGPUResponse, }; pub const PRESENTATION_BUFFER_COUNT: usize = 10; @@ -64,6 +67,34 @@ impl DeviceScope { } } +/// This roughly matches <https://www.w3.org/TR/2024/WD-webgpu-20240703/#encoder-state> +#[derive(Debug, Default, Eq, PartialEq)] +enum Pass<P: ?Sized> { + /// Pass is open (not ended) + Open { + /// Actual pass + pass: Box<P>, + /// we need to store valid field + /// because wgpu does not invalidate pass on error + valid: bool, + }, + /// When pass is ended we need to drop it so we replace it with this + #[default] + Ended, +} + +impl<P: ?Sized> Pass<P> { + /// Creates new open pass + fn new(pass: Box<P>, valid: bool) -> Self { + Self::Open { pass, valid } + } + + /// Replaces pass with ended + fn take(&mut self) -> Self { + std::mem::take(self) + } +} + #[allow(clippy::upper_case_acronyms)] // Name of the library pub(crate) struct WGPU { receiver: IpcReceiver<WebGPURequest>, @@ -85,9 +116,10 @@ pub(crate) struct WGPU { wgpu_image_map: Arc<Mutex<HashMap<u64, PresentationData>>>, /// Provides access to poller thread poller: Poller, - /// Store compute passes (that have not ended yet) and their validity - compute_passes: HashMap<ComputePassId, (Box<dyn DynComputePass>, bool)>, - //render_passes: HashMap<RenderPassId, Box<dyn DynRenderPass>>, + /// Store compute passes + compute_passes: HashMap<ComputePassId, Pass<dyn DynComputePass>>, + /// Store render passes + render_passes: HashMap<RenderPassId, Pass<dyn DynRenderPass>>, } impl WGPU { @@ -132,6 +164,7 @@ impl WGPU { external_images, wgpu_image_map, compute_passes: HashMap::new(), + render_passes: HashMap::new(), } } @@ -774,7 +807,7 @@ impl WGPU { )); assert!( self.compute_passes - .insert(compute_pass_id, (pass, error.is_none())) + .insert(compute_pass_id, Pass::new(pass, error.is_none())) .is_none(), "ComputePass should not exist yet." ); @@ -786,7 +819,11 @@ impl WGPU { pipeline_id, device_id, } => { - if let Some((pass, valid)) = self.compute_passes.get_mut(&compute_pass_id) { + let pass = self + .compute_passes + .get_mut(&compute_pass_id) + .expect("ComputePass should exists"); + if let Pass::Open { pass, valid } = pass { *valid &= pass.set_pipeline(&self.global, pipeline_id).is_ok(); } else { self.maybe_dispatch_error( @@ -802,7 +839,11 @@ impl WGPU { offsets, device_id, } => { - if let Some((pass, valid)) = self.compute_passes.get_mut(&compute_pass_id) { + let pass = self + .compute_passes + .get_mut(&compute_pass_id) + .expect("ComputePass should exists"); + if let Pass::Open { pass, valid } = pass { *valid &= pass .set_bind_group(&self.global, index, bind_group_id, &offsets) .is_ok(); @@ -820,7 +861,11 @@ impl WGPU { z, device_id, } => { - if let Some((pass, valid)) = self.compute_passes.get_mut(&compute_pass_id) { + let pass = self + .compute_passes + .get_mut(&compute_pass_id) + .expect("ComputePass should exists"); + if let Pass::Open { pass, valid } = pass { *valid &= pass.dispatch_workgroups(&self.global, x, y, z).is_ok(); } else { self.maybe_dispatch_error( @@ -835,7 +880,11 @@ impl WGPU { offset, device_id, } => { - if let Some((pass, valid)) = self.compute_passes.get_mut(&compute_pass_id) { + let pass = self + .compute_passes + .get_mut(&compute_pass_id) + .expect("ComputePass should exists"); + if let Pass::Open { pass, valid } = pass { *valid &= pass .dispatch_workgroups_indirect(&self.global, buffer_id, offset) .is_ok(); @@ -851,10 +900,15 @@ impl WGPU { device_id, command_encoder_id, } => { + // https://www.w3.org/TR/2024/WD-webgpu-20240703/#dom-gpucomputepassencoder-end + let pass = self + .compute_passes + .get_mut(&compute_pass_id) + .expect("ComputePass should exists"); // TODO: Command encoder state error - if let Some((mut pass, valid)) = - self.compute_passes.remove(&compute_pass_id) - { + if let Pass::Open { mut pass, valid } = pass.take() { + // `pass.end` does step 1-4 + // and if it returns ok we check the validity of the pass at step 5 if pass.end(&self.global).is_ok() && !valid { self.encoder_record_error( command_encoder_id, @@ -868,21 +922,81 @@ impl WGPU { ); }; }, + WebGPURequest::BeginRenderPass { + command_encoder_id, + render_pass_id, + label, + color_attachments, + depth_stencil_attachment, + device_id: _device_id, + } => { + let global = &self.global; + let desc = &RenderPassDescriptor { + label, + color_attachments: color_attachments.into(), + depth_stencil_attachment: depth_stencil_attachment.as_ref(), + timestamp_writes: None, + occlusion_query_set: None, + }; + let (pass, error) = gfx_select!( + command_encoder_id => global.command_encoder_create_render_pass_dyn( + command_encoder_id, + desc, + )); + assert!( + self.render_passes + .insert(render_pass_id, Pass::new(pass, error.is_none())) + .is_none(), + "RenderPass should not exist yet." + ); + // TODO: Command encoder state errors + // self.maybe_dispatch_wgpu_error(device_id, error); + }, + WebGPURequest::RenderPassCommand { + render_pass_id, + render_command, + device_id, + } => { + let pass = self + .render_passes + .get_mut(&render_pass_id) + .expect("RenderPass should exists"); + if let Pass::Open { pass, valid } = pass { + *valid &= + apply_render_command(&self.global, pass, render_command).is_ok(); + } else { + self.maybe_dispatch_error( + device_id, + Some(Error::Validation("pass already ended".to_string())), + ); + }; + }, WebGPURequest::EndRenderPass { - render_pass, + render_pass_id, device_id, + command_encoder_id, } => { - if let Some(render_pass) = render_pass { - let command_encoder_id = render_pass.parent_id(); - let global = &self.global; - let result = gfx_select!(command_encoder_id => global.render_pass_end(&render_pass)); - self.maybe_dispatch_wgpu_error(device_id, result.err()) + // https://www.w3.org/TR/2024/WD-webgpu-20240703/#dom-gpurenderpassencoder-end + let pass = self + .render_passes + .get_mut(&render_pass_id) + .expect("RenderPass should exists"); + // TODO: Command encoder state error + if let Pass::Open { mut pass, valid } = pass.take() { + // `pass.end` does step 1-4 + // and if it returns ok we check the validity of the pass at step 5 + if pass.end(&self.global).is_ok() && !valid { + self.encoder_record_error( + command_encoder_id, + &Err::<(), _>("Pass is invalid".to_string()), + ); + } } else { self.dispatch_error( device_id, - Error::Validation("Render pass already ended".to_string()), - ) - } + Error::Validation("Pass already ended".to_string()), + ); + }; }, WebGPURequest::Submit { queue_id, @@ -1171,12 +1285,20 @@ impl WGPU { }; }, WebGPURequest::DropComputePass(id) => { - // Compute pass might have already ended + // Pass might have already ended. self.compute_passes.remove(&id); if let Err(e) = self.script_sender.send(WebGPUMsg::FreeComputePass(id)) { warn!("Unable to send FreeComputePass({:?}) ({:?})", id, e); }; }, + WebGPURequest::DropRenderPass(id) => { + self.render_passes + .remove(&id) + .expect("RenderPass should exists"); + if let Err(e) = self.script_sender.send(WebGPUMsg::FreeRenderPass(id)) { + warn!("Unable to send FreeRenderPass({:?}) ({:?})", id, e); + }; + }, WebGPURequest::DropRenderPipeline(id) => { let global = &self.global; gfx_select!(id => global.render_pipeline_drop(id)); |