diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 351a2f42b01..9b3af8236e1 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -168,7 +168,7 @@ use servo_rand::{Rng, ServoRng, SliceRandom, random}; use servo_url::{Host, ImmutableOrigin, ServoUrl}; use style::global_style_data::StyleThreadPool; #[cfg(feature = "webgpu")] -use webgpu::swapchain::WGPUImageMap; +use webgpu::canvas_context::WGPUImageMap; #[cfg(feature = "webgpu")] use webgpu_traits::{WebGPU, WebGPURequest}; use webrender::RenderApiSender; diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 39392d68a57..dbe8885176a 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -117,8 +117,6 @@ use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayout}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::trace::{HashMapTracedValues, NoTrace}; -#[cfg(feature = "webgpu")] -use crate::dom::bindings::weakref::WeakRef; use crate::dom::bindings::xmlname::matches_name_production; use crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D; use crate::dom::cdatasection::CDATASection; @@ -280,9 +278,6 @@ pub(crate) enum DeclarativeRefresh { }, CreatedAfterLoad, } -#[cfg(feature = "webgpu")] -pub(crate) type WebGPUContextsMap = - Rc>>>; /// #[dom_struct] @@ -485,10 +480,9 @@ pub(crate) struct Document { DomRefCell>>, /// Whether or not animated images need to have their contents updated. has_pending_animated_image_update: Cell, - /// List of all WebGPU contexts. + /// List of all WebGPU contexts that need flushing. #[cfg(feature = "webgpu")] - #[ignore_malloc_size_of = "Rc are hard"] - webgpu_contexts: WebGPUContextsMap, + dirty_webgpu_contexts: DomRefCell>>, /// selection: MutNullableDom, /// A timeline for animations which is used for synchronizing animations. @@ -2648,8 +2642,11 @@ impl Document { } #[cfg(feature = "webgpu")] - pub(crate) fn webgpu_contexts(&self) -> WebGPUContextsMap { - self.webgpu_contexts.clone() + pub(crate) fn add_dirty_webgpu_context(&self, context: &GPUCanvasContext) { + self.dirty_webgpu_contexts + .borrow_mut() + .entry(context.context_id()) + .or_insert_with(|| Dom::from_ref(context)); } /// Whether or not this [`Document`] needs a rendering update, due to changed @@ -2706,12 +2703,11 @@ impl Document { #[cfg(feature = "webgpu")] image_keys.extend( - self.webgpu_contexts + self.dirty_webgpu_contexts .borrow_mut() - .iter() - .filter_map(|(_, context)| context.root()) - .filter(|context| context.update_rendering(canvas_epoch)) - .map(|context| context.image_key()), + .drain() + .filter(|(_, context)| context.update_rendering(canvas_epoch)) + .map(|(_, context)| context.image_key()), ); image_keys.extend( @@ -3426,7 +3422,7 @@ impl Document { dirty_webgl_contexts: DomRefCell::new(HashMapTracedValues::new()), has_pending_animated_image_update: Cell::new(false), #[cfg(feature = "webgpu")] - webgpu_contexts: Rc::new(RefCell::new(HashMapTracedValues::new())), + dirty_webgpu_contexts: DomRefCell::new(HashMapTracedValues::new()), selection: MutNullableDom::new(None), animation_timeline: if pref!(layout_animations_test_enabled) { DomRefCell::new(AnimationTimeline::new_for_testing()) diff --git a/components/script/dom/webgpu/gpucanvascontext.rs b/components/script/dom/webgpu/gpucanvascontext.rs index a00b2ec56c1..63d7e09559a 100644 --- a/components/script/dom/webgpu/gpucanvascontext.rs +++ b/components/script/dom/webgpu/gpucanvascontext.rs @@ -3,19 +3,20 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::borrow::Cow; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use arrayvec::ArrayVec; use base::Epoch; use dom_struct::dom_struct; use ipc_channel::ipc::{self}; use pixels::Snapshot; +use script_bindings::codegen::GenericBindings::WebGPUBinding::GPUTextureFormat; +use script_bindings::inheritance::Castable; use webgpu_traits::{ - ContextConfiguration, PRESENTATION_BUFFER_COUNT, WebGPU, WebGPUContextId, WebGPURequest, - WebGPUTexture, + ContextConfiguration, PRESENTATION_BUFFER_COUNT, PendingTexture, WebGPU, WebGPUContextId, + WebGPURequest, }; -use webrender_api::ImageKey; -use webrender_api::units::DeviceIntSize; +use webrender_api::{ImageFormat, ImageKey}; use wgpu_core::id; use super::gpuconvert::convert_texture_descriptor; @@ -24,24 +25,20 @@ use crate::canvas_context::{ CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas, LayoutCanvasRenderingContextHelpers, }; -use crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods; use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods; use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ GPUCanvasAlphaMode, GPUCanvasConfiguration, GPUDeviceMethods, GPUExtent3D, GPUExtent3DDict, - GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat, - GPUTextureUsageConstants, + GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension, GPUTextureUsageConstants, }; use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::USVString; -use crate::dom::bindings::weakref::WeakRef; -use crate::dom::document::WebGPUContextsMap; use crate::dom::globalscope::GlobalScope; use crate::dom::html::htmlcanvaselement::HTMLCanvasElement; -use crate::dom::node::NodeTraits; +use crate::dom::node::{Node, NodeDamage, NodeTraits}; use crate::script_runtime::CanGc; /// @@ -53,18 +50,6 @@ fn supported_context_format(format: GPUTextureFormat) -> bool { ) } -#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)] -/// Helps observe changes on swapchain -struct DrawingBuffer { - #[no_trace] - size: DeviceIntSize, - /// image is transparent black - cleared: bool, - #[ignore_malloc_size_of = "Defined in wgpu"] - #[no_trace] - config: Option, -} - #[dom_struct] pub(crate) struct GPUCanvasContext { reflector_: Reflector, @@ -73,7 +58,6 @@ pub(crate) struct GPUCanvasContext { channel: WebGPU, /// canvas: HTMLCanvasElementOrOffscreenCanvas, - // TODO: can we have wgpu surface that is hw accelerated inside wr ... #[ignore_malloc_size_of = "Defined in webrender"] #[no_trace] webrender_image: ImageKey, @@ -84,13 +68,11 @@ pub(crate) struct GPUCanvasContext { configuration: RefCell>, /// texture_descriptor: RefCell>, - /// Conceptually - drawing_buffer: RefCell, /// current_texture: MutNullableDom, - /// This is used for clearing - #[ignore_malloc_size_of = "Rc are hard"] - webgpu_contexts: WebGPUContextsMap, + /// Set if image is cleared + /// (usually done by [`GPUCanvasContext::replace_drawing_buffer`]) + cleared: Cell, } impl GPUCanvasContext { @@ -99,7 +81,6 @@ impl GPUCanvasContext { global: &GlobalScope, canvas: HTMLCanvasElementOrOffscreenCanvas, channel: WebGPU, - webgpu_contexts: WebGPUContextsMap, ) -> Self { let (sender, receiver) = ipc::channel().unwrap(); let size = canvas.size().cast().cast_unit(); @@ -121,15 +102,10 @@ impl GPUCanvasContext { canvas, webrender_image, context_id: WebGPUContextId(external_id.0), - drawing_buffer: RefCell::new(DrawingBuffer { - size, - cleared: true, - ..Default::default() - }), configuration: RefCell::new(None), texture_descriptor: RefCell::new(None), current_texture: MutNullableDom::default(), - webgpu_contexts, + cleared: Cell::new(true), } } @@ -139,45 +115,38 @@ impl GPUCanvasContext { channel: WebGPU, can_gc: CanGc, ) -> DomRoot { - let document = canvas.owner_document(); - let this = reflect_dom_object( + reflect_dom_object( Box::new(GPUCanvasContext::new_inherited( global, HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)), channel, - document.webgpu_contexts(), )), global, can_gc, - ); - this.webgpu_contexts - .borrow_mut() - .entry(this.context_id()) - .or_insert_with(|| WeakRef::new(&this)); - this + ) } } // Abstract ops from spec impl GPUCanvasContext { /// - fn texture_descriptor_for_canvas( + fn texture_descriptor_for_canvas_and_configuration( &self, configuration: &GPUCanvasConfiguration, ) -> GPUTextureDescriptor { let size = self.size(); GPUTextureDescriptor { - format: configuration.format, - // We need to add `COPY_SRC` so we can copy texture to presentation buffer - // causes FAIL on webgpu:web_platform,canvas,configure:usage:* - usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC, size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict { width: size.width, height: size.height, depthOrArrayLayers: 1, }), + format: configuration.format, + // We need to add `COPY_SRC` so we can copy texture to presentation buffer + // causes FAIL on webgpu:web_platform,canvas,configure:usage:* + usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC, viewFormats: configuration.viewFormats.clone(), - // other members to default + // All other members set to their defaults. mipLevelCount: 1, sampleCount: 1, parent: GPUObjectDescriptorBase { @@ -188,89 +157,65 @@ impl GPUCanvasContext { } /// - fn expire_current_texture(&self, canvas_epoch: Option) -> bool { - // Step 1: If context.[[currentTexture]] is not null: - let Some(current_texture) = self.current_texture.take() else { - return false; - }; + fn expire_current_texture(&self, skip_dirty: bool) { + // 1. If context.[[currentTexture]] is not null: - // Make copy of texture content - let did_swap = self.send_swap_chain_present(current_texture.id(), canvas_epoch); + if let Some(current_texture) = self.current_texture.take() { + // 1.2 Set context.[[currentTexture]] to null. - // Step 1.1: Call context.currentTexture.destroy() (without destroying - // context.drawingBuffer) to terminate write access to the image. - current_texture.Destroy(); - - // Step 1.2: Set context.[[currentTexture]] to null. - // This is handled by the call to `.take()` above. - - did_swap + // 1.1 Call context.[[currentTexture]].destroy() + // (without destroying context.[[drawingBuffer]]) + // to terminate write access to the image. + current_texture.Destroy() + // we can safely destroy content here, + // because we already copied content when doing present + // or current texture is getting cleared + } + // We skip marking the canvas as dirty again if we are already + // in the process of updating the rendering. + if !skip_dirty { + // texture is either cleared or applied to canvas + self.mark_as_dirty(); + } } /// fn replace_drawing_buffer(&self) { - // Step 1 - self.expire_current_texture(None); - // Step 2 - let configuration = self.configuration.borrow(); - // Step 3 - let mut drawing_buffer = self.drawing_buffer.borrow_mut(); - drawing_buffer.size = self.size().cast().cast_unit(); - drawing_buffer.cleared = true; - if let Some(configuration) = configuration.as_ref() { - drawing_buffer.config = Some(ContextConfiguration { - device_id: configuration.device.id().0, - queue_id: configuration.device.queue_id().0, - format: configuration.format.convert(), - is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque), - }); - } else { - drawing_buffer.config.take(); - }; - // TODO: send less - self.channel - .0 - .send(WebGPURequest::UpdateContext { - context_id: self.context_id, - size: drawing_buffer.size, - configuration: drawing_buffer.config, - }) - .expect("Failed to update webgpu context"); + // 1. Expire the current texture of context. + self.expire_current_texture(false); + // 2. Let configuration be context.[[configuration]]. + // 3. Set context.[[drawingBuffer]] to + // a transparent black image of the same size as context.canvas + self.cleared.set(true); } } // Internal helper methods impl GPUCanvasContext { - fn layout_handle(&self) -> Option { - if self.drawing_buffer.borrow().cleared { - None - } else { - Some(self.webrender_image) - } + fn context_configuration(&self) -> Option { + let configuration = self.configuration.borrow(); + let configuration = configuration.as_ref()?; + Some(ContextConfiguration { + device_id: configuration.device.id().0, + queue_id: configuration.device.queue_id().0, + format: match configuration.format { + GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8, + GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8, + _ => unreachable!("Configure method should set valid texture format"), + }, + is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque), + size: self.size(), + }) } - fn send_swap_chain_present( - &self, - texture_id: WebGPUTexture, - canvas_epoch: Option, - ) -> bool { - self.drawing_buffer.borrow_mut().cleared = false; - let encoder_id = self.global().wgpu_id_hub().create_command_encoder_id(); - let send_result = self.channel.0.send(WebGPURequest::SwapChainPresent { - context_id: self.context_id, - texture_id: texture_id.0, - encoder_id, - canvas_epoch, - }); - - if let Err(error) = &send_result { - warn!( - "Failed to send UpdateWebrenderData({:?}) ({error})", - self.context_id, - ); - } - - send_result.is_ok() + fn pending_texture(&self) -> Option { + self.current_texture.get().map(|texture| PendingTexture { + texture_id: texture.id().0, + encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(), + configuration: self + .context_configuration() + .expect("Context should be configured if there is a texture."), + }) } } @@ -281,32 +226,45 @@ impl CanvasContext for GPUCanvasContext { self.context_id } - /// - fn update_rendering(&self, canvas_epoch: Epoch) -> bool { - if !self.onscreen() { - return false; - } - - // Step 1: Expire the current texture of context. - self.expire_current_texture(Some(canvas_epoch)) - // Step 2: Set context.[[lastPresentedImage]] to context.[[drawingBuffer]]. - // TODO: Implement this. - } - fn image_key(&self) -> Option { Some(self.webrender_image) } + /// + fn update_rendering(&self, canvas_epoch: Epoch) -> bool { + // Present by updating the image in WebRender. This will copy the texture into + // the presentation buffer and use it for presenting or send a cleared image to WebRender. + if let Err(error) = self.channel.0.send(WebGPURequest::Present { + context_id: self.context_id, + pending_texture: self.pending_texture(), + size: self.size(), + canvas_epoch, + }) { + warn!( + "Failed to send WebGPURequest::Present({:?}) ({error})", + self.context_id + ); + } + + // 1. Expire the current texture of context. + self.expire_current_texture(true); + + true + } + /// fn resize(&self) { - // Step 1 + // 1. Replace the drawing buffer of context. self.replace_drawing_buffer(); - // Step 2 + // 2. Let configuration be context.[[configuration]] let configuration = self.configuration.borrow(); - // Step 3 + // 3. If configuration is not null: if let Some(configuration) = configuration.as_ref() { - self.texture_descriptor - .replace(Some(self.texture_descriptor_for_canvas(configuration))); + // 3.1. Set context.[[textureDescriptor]] to the + // GPUTextureDescriptor for the canvas and configuration(canvas, configuration). + self.texture_descriptor.replace(Some( + self.texture_descriptor_for_canvas_and_configuration(configuration), + )); } } @@ -317,7 +275,7 @@ impl CanvasContext for GPUCanvasContext { /// fn get_image_data(&self) -> Option { // 1. Return a copy of the image contents of context. - Some(if self.drawing_buffer.borrow().cleared { + Some(if self.cleared.get() { Snapshot::cleared(self.size()) } else { let (sender, receiver) = ipc::channel().unwrap(); @@ -325,21 +283,30 @@ impl CanvasContext for GPUCanvasContext { .0 .send(WebGPURequest::GetImage { context_id: self.context_id, + // We need to read from the pending texture, if one exists. + pending_texture: self.pending_texture(), sender, }) - .unwrap(); - receiver.recv().unwrap().to_owned() + .ok()?; + receiver.recv().ok()?.to_owned() }) } fn canvas(&self) -> Option { Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)) } + + fn mark_as_dirty(&self) { + if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) = self.canvas { + canvas.upcast::().dirty(NodeDamage::Other); + canvas.owner_document().add_dirty_webgpu_context(self); + } + } } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> { fn canvas_data_source(self) -> Option { - (*self.unsafe_get()).layout_handle() + (*self.unsafe_get()).image_key() } } @@ -351,19 +318,20 @@ impl GPUCanvasContextMethods for GPUCanvasContext { /// fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> { - // Step 1: Let device be configuration.device + // 1. Let device be configuration.device let device = &configuration.device; - // Step 5: Let descriptor be the GPUTextureDescriptor for the canvas and configuration. - let descriptor = self.texture_descriptor_for_canvas(configuration); + // 5. Let descriptor be the GPUTextureDescriptor for the canvas and configuration. + let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration); - // Step 2&3: Validate texture format required features - let (mut desc, _) = convert_texture_descriptor(&descriptor, device)?; - desc.label = Some(Cow::Borrowed( + // 2. Validate texture format required features of configuration.format with device.[[device]]. + // 3. Validate texture format required features of each element of configuration.viewFormats with device.[[device]]. + let (mut wgpu_descriptor, _) = convert_texture_descriptor(&descriptor, device)?; + wgpu_descriptor.label = Some(Cow::Borrowed( "dummy texture for texture descriptor validation", )); - // Step 4: If Supported context formats does not contain configuration.format, throw a TypeError + // 4. If Supported context formats does not contain configuration.format, throw a TypeError if !supported_context_format(configuration.format) { return Err(Error::Type(format!( "Unsupported context format: {:?}", @@ -371,23 +339,23 @@ impl GPUCanvasContextMethods for GPUCanvasContext { ))); } - // Step 5 + // 6. Let this.[[configuration]] to configuration. self.configuration.replace(Some(configuration.clone())); - // Step 6 + // 7. Set this.[[textureDescriptor]] to descriptor. self.texture_descriptor.replace(Some(descriptor)); - // Step 7 + // 8. Replace the drawing buffer of this. self.replace_drawing_buffer(); - // Step 8: Validate texture descriptor + // 9. Validate texture descriptor let texture_id = self.global().wgpu_id_hub().create_texture_id(); self.channel .0 .send(WebGPURequest::ValidateTextureDescriptor { device_id: device.id().0, texture_id, - descriptor: desc, + descriptor: wgpu_descriptor, }) .expect("Failed to create WebGPU SwapChain"); @@ -396,45 +364,49 @@ impl GPUCanvasContextMethods for GPUCanvasContext { /// fn Unconfigure(&self) { - // Step 1 + // 1. Set this.[[configuration]] to null. self.configuration.take(); - // Step 2 + // 2. Set this.[[textureDescriptor]] to null. self.current_texture.take(); - // Step 3 + // 3. Replace the drawing buffer of this. self.replace_drawing_buffer(); } /// fn GetCurrentTexture(&self) -> Fallible> { - // Step 1: If this.[[configuration]] is null, throw an InvalidStateError and return. + // 1. If this.[[configuration]] is null, throw an InvalidStateError and return. let configuration = self.configuration.borrow(); let Some(configuration) = configuration.as_ref() else { return Err(Error::InvalidState); }; - // Step 2: Assert this.[[textureDescriptor]] is not null. + // 2. Assert this.[[textureDescriptor]] is not null. let texture_descriptor = self.texture_descriptor.borrow(); let texture_descriptor = texture_descriptor.as_ref().unwrap(); - // Step 6 + // 3. Let device be this.[[configuration]].device. + let device = &configuration.device; let current_texture = if let Some(current_texture) = self.current_texture.get() { current_texture } else { - // Step 4.1 + // If this.[[currentTexture]] is null: + // 4.1. Replace the drawing buffer of this. self.replace_drawing_buffer(); - // Step 4.2 - let current_texture = configuration.device.CreateTexture(texture_descriptor)?; + // 4.2. Set this.[[currentTexture]] to the result of calling device.createTexture() with this.[[textureDescriptor]], + // except with the GPUTexture’s underlying storage pointing to this.[[drawingBuffer]]. + let current_texture = device.CreateTexture(texture_descriptor)?; self.current_texture.set(Some(¤t_texture)); - // We only need to mark new texture - self.mark_as_dirty(); + + // The content of the texture is the content of the canvas. + self.cleared.set(false); + current_texture }; - // Step 6 + // 6. Return this.[[currentTexture]]. Ok(current_texture) } } impl Drop for GPUCanvasContext { fn drop(&mut self) { - self.webgpu_contexts.borrow_mut().remove(&self.context_id()); if let Err(e) = self.channel.0.send(WebGPURequest::DestroyContext { context_id: self.context_id, }) { diff --git a/components/servo/lib.rs b/components/servo/lib.rs index b8e97af4504..58e9c97e8ed 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -113,7 +113,7 @@ use webgl::WebGLComm; #[cfg(feature = "webgpu")] pub use webgpu; #[cfg(feature = "webgpu")] -use webgpu::swapchain::WGPUImageMap; +use webgpu::canvas_context::WGPUImageMap; use webrender::{ONE_TIME_USAGE_HINT, RenderApiSender, ShaderPrecacheFlags, UploadMethod}; use webrender_api::{ColorF, DocumentId, FramePublishId}; use webview::WebViewInner; diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 9282b10acea..7410b7e7f3a 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -523,13 +523,9 @@ impl ExternalImageHandler for WebrenderExternalImageHandlers { }, WebrenderImageHandlerType::WebGPU => { let (source, size) = self.webgpu_handler.as_mut().unwrap().lock(key.0); - let buffer = match source { - ExternalImageSource::RawData(b) => b, - _ => panic!("Wrong type"), - }; ExternalImage { uv: TexelRect::new(0.0, size.height as f32, size.width as f32, 0.0), - source: ExternalImageSource::RawData(buffer), + source, } }, } diff --git a/components/shared/webgpu/lib.rs b/components/shared/webgpu/lib.rs index b03a82c1996..36d329d4451 100644 --- a/components/shared/webgpu/lib.rs +++ b/components/shared/webgpu/lib.rs @@ -11,7 +11,8 @@ use std::ops::Range; use ipc_channel::ipc::{IpcSender, IpcSharedMemory}; use serde::{Deserialize, Serialize}; -use webrender_api::ImageFormat; +use webrender_api::euclid::default::Size2D; +use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat}; use wgpu_core::device::HostMap; pub use wgpu_core::id::markers::{ ComputePassEncoder as ComputePass, RenderPassEncoder as RenderPass, @@ -22,7 +23,7 @@ pub use wgpu_core::id::{ use wgpu_core::id::{ComputePipelineId, DeviceId, QueueId, RenderPipelineId}; use wgpu_core::instance::FailedLimit; use wgpu_core::pipeline::CreateShaderModuleError; -use wgpu_types::{AdapterInfo, DeviceDescriptor, Features, Limits, TextureFormat}; +use wgpu_types::{AdapterInfo, COPY_BYTES_PER_ROW_ALIGNMENT, DeviceDescriptor, Features, Limits}; pub use crate::error::*; pub use crate::ids::*; @@ -60,17 +61,34 @@ pub struct Adapter { pub struct ContextConfiguration { pub device_id: DeviceId, pub queue_id: QueueId, - pub format: TextureFormat, + pub format: ImageFormat, pub is_opaque: bool, + pub size: Size2D, } impl ContextConfiguration { - pub fn format(&self) -> ImageFormat { - match self.format { - TextureFormat::Rgba8Unorm => ImageFormat::RGBA8, - TextureFormat::Bgra8Unorm => ImageFormat::BGRA8, - // TODO: wgt::TextureFormat::Rgba16Float - _ => unreachable!("Unsupported canvas context format in configuration"), + pub fn stride(&self) -> u32 { + (self.size.width * self.format.bytes_per_pixel() as u32) + .next_multiple_of(COPY_BYTES_PER_ROW_ALIGNMENT) + } + + pub fn buffer_size(&self) -> u64 { + self.stride() as u64 * self.size.height as u64 + } +} + +impl From for ImageDescriptor { + fn from(config: ContextConfiguration) -> Self { + ImageDescriptor { + format: config.format, + size: config.size.cast().cast_unit(), + stride: Some(config.stride() as i32), + offset: 0, + flags: if config.is_opaque { + ImageDescriptorFlags::IS_OPAQUE + } else { + ImageDescriptorFlags::empty() + }, } } } diff --git a/components/shared/webgpu/messages/recv.rs b/components/shared/webgpu/messages/recv.rs index b273f6f7557..e38ee1d323e 100644 --- a/components/shared/webgpu/messages/recv.rs +++ b/components/shared/webgpu/messages/recv.rs @@ -12,6 +12,7 @@ use ipc_channel::ipc::{IpcSender, IpcSharedMemory}; use pixels::IpcSnapshot; use serde::{Deserialize, Serialize}; use webrender_api::ImageKey; +use webrender_api::euclid::default::Size2D; use webrender_api::units::DeviceIntSize; use wgpu_core::Label; use wgpu_core::binding_model::{ @@ -52,6 +53,13 @@ use crate::{ WebGPURenderPipelineResponse, }; +#[derive(Debug, Deserialize, Serialize)] +pub struct PendingTexture { + pub texture_id: TextureId, + pub encoder_id: CommandEncoderId, + pub configuration: ContextConfiguration, +} + #[derive(Debug, Deserialize, Serialize)] pub enum WebGPURequest { BufferMapAsync { @@ -152,22 +160,18 @@ pub enum WebGPURequest { size: DeviceIntSize, sender: IpcSender<(WebGPUContextId, ImageKey)>, }, - /// Recreates swapchain (if needed) - UpdateContext { + /// Present texture to WebRender + Present { context_id: WebGPUContextId, - size: DeviceIntSize, - configuration: Option, + pending_texture: Option, + size: Size2D, + canvas_epoch: Epoch, }, - /// Reads texture to swapchains buffer and maps it - SwapChainPresent { - context_id: WebGPUContextId, - texture_id: TextureId, - encoder_id: CommandEncoderId, - canvas_epoch: Option, - }, - /// Obtains image from latest presentation buffer (same as wr update) + /// Create [`pixels::Snapshot`] with contents of the last present operation + /// or provided pending texture and send it over provided [`IpcSender`]. GetImage { context_id: WebGPUContextId, + pending_texture: Option, sender: IpcSender, }, ValidateTextureDescriptor { diff --git a/components/webgpu/canvas_context.rs b/components/webgpu/canvas_context.rs new file mode 100644 index 00000000000..f0957309c44 --- /dev/null +++ b/components/webgpu/canvas_context.rs @@ -0,0 +1,776 @@ +/* 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/. */ + +//! Main process implementation of [GPUCanvasContext](https://www.w3.org/TR/webgpu/#canvas-context) + +use std::ptr::NonNull; +use std::sync::{Arc, Mutex}; + +use arrayvec::ArrayVec; +use base::Epoch; +use compositing_traits::{ + CrossProcessCompositorApi, ExternalImageSource, SerializableImageData, + WebrenderExternalImageApi, +}; +use euclid::default::Size2D; +use ipc_channel::ipc::IpcSender; +use log::warn; +use pixels::{IpcSnapshot, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; +use rustc_hash::FxHashMap; +use webgpu_traits::{ + ContextConfiguration, PRESENTATION_BUFFER_COUNT, PendingTexture, WebGPUContextId, WebGPUMsg, +}; +use webrender_api::units::DeviceIntSize; +use webrender_api::{ + ExternalImageData, ExternalImageId, ExternalImageType, ImageDescriptor, ImageDescriptorFlags, + ImageFormat, ImageKey, +}; +use wgpu_core::device::HostMap; +use wgpu_core::global::Global; +use wgpu_core::id::{ + self, BufferId, CommandBufferId, CommandEncoderId, DeviceId, QueueId, TextureId, +}; +use wgpu_core::resource::{ + BufferAccessError, BufferDescriptor, BufferMapOperation, CreateBufferError, +}; +use wgpu_types::{ + BufferUsages, COPY_BYTES_PER_ROW_ALIGNMENT, CommandBufferDescriptor, CommandEncoderDescriptor, + Extent3d, Origin3d, TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, + TextureAspect, +}; + +pub type WGPUImageMap = Arc>>; + +const fn image_data(context_id: WebGPUContextId) -> ExternalImageData { + ExternalImageData { + id: ExternalImageId(context_id.0), + channel_index: 0, + image_type: ExternalImageType::Buffer, + normalized_uvs: false, + } +} + +/// Allocated buffer on GPU device +#[derive(Clone, Copy, Debug)] +struct Buffer { + device_id: DeviceId, + queue_id: QueueId, + size: u64, +} + +impl Buffer { + /// Returns true if buffer is compatible with provided configuration + fn has_compatible_config(&self, config: &ContextConfiguration) -> bool { + config.device_id == self.device_id && self.size == config.buffer_size() + } +} + +/// Mapped GPUBuffer +#[derive(Debug)] +struct MappedBuffer { + buffer: Buffer, + data: NonNull, + len: u64, + image_size: Size2D, + image_format: ImageFormat, + is_opaque: bool, +} + +// Mapped buffer can be shared between safely (it's read-only) +unsafe impl Send for MappedBuffer {} + +impl MappedBuffer { + const fn slice(&'_ self) -> &'_ [u8] { + // Safety: Pointer is from wgpu, and we only use it here + unsafe { std::slice::from_raw_parts(self.data.as_ptr(), self.len as usize) } + } + + fn stride(&self) -> u32 { + (self.image_size.width * self.image_format.bytes_per_pixel() as u32) + .next_multiple_of(COPY_BYTES_PER_ROW_ALIGNMENT) + } +} + +#[derive(Debug)] +enum StagingBufferState { + /// The Initial state: the buffer has yet to be created with only an + /// id reserved for it. + Unassigned, + /// The buffer is allocated in the WGPU Device and is ready to be used. + Available(Buffer), + /// `mapAsync` is currently running on the buffer. + Mapping(Buffer), + /// The buffer is currently mapped. + Mapped(MappedBuffer), +} + +/// A staging buffer used for texture to buffer to CPU copy operations. +#[derive(Debug)] +struct StagingBuffer { + global: Arc, + buffer_id: BufferId, + state: StagingBufferState, +} + +// [`StagingBuffer`] only used for reading (never for writing) +// so it is safe to share between threads. +unsafe impl Sync for StagingBuffer {} + +impl StagingBuffer { + fn new(global: Arc, buffer_id: BufferId) -> Self { + Self { + global, + buffer_id, + state: StagingBufferState::Unassigned, + } + } + + const fn is_mapped(&self) -> bool { + matches!(self.state, StagingBufferState::Mapped(..)) + } + + /// Return true if buffer can be used directly with provided config + /// without any additional work + fn is_available_and_has_compatible_config(&self, config: &ContextConfiguration) -> bool { + let StagingBufferState::Available(buffer) = &self.state else { + return false; + }; + buffer.has_compatible_config(config) + } + + /// Return true if buffer is not mapping or being mapped + const fn needs_assignment(&self) -> bool { + matches!( + self.state, + StagingBufferState::Unassigned | StagingBufferState::Available(_) + ) + } + + /// Make buffer available by unmapping / destroying it and then recreating it if needed. + fn ensure_available(&mut self, config: &ContextConfiguration) -> Result<(), CreateBufferError> { + let recreate = match &self.state { + StagingBufferState::Unassigned => true, + StagingBufferState::Available(buffer) | + StagingBufferState::Mapping(buffer) | + StagingBufferState::Mapped(MappedBuffer { buffer, .. }) => { + if buffer.has_compatible_config(config) { + let _ = self.global.buffer_unmap(self.buffer_id); + false + } else { + self.global.buffer_drop(self.buffer_id); + true + } + }, + }; + if recreate { + let buffer_size = config.buffer_size(); + let (_, error) = self.global.device_create_buffer( + config.device_id, + &BufferDescriptor { + label: None, + size: buffer_size, + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + mapped_at_creation: false, + }, + Some(self.buffer_id), + ); + if let Some(error) = error { + return Err(error); + }; + self.state = StagingBufferState::Available(Buffer { + device_id: config.device_id, + queue_id: config.queue_id, + size: buffer_size, + }); + } + Ok(()) + } + + /// Makes buffer available and prepares command encoder + /// that will copy texture to this staging buffer. + /// + /// Caller must submit command buffer to queue. + fn prepare_load_texture_command_buffer( + &mut self, + texture_id: TextureId, + encoder_id: CommandEncoderId, + config: &ContextConfiguration, + ) -> Result> { + self.ensure_available(config)?; + let StagingBufferState::Available(buffer) = &self.state else { + unreachable!("Should be made available by `ensure_available`") + }; + let device_id = buffer.device_id; + let command_descriptor = CommandEncoderDescriptor { label: None }; + let (encoder_id, error) = self.global.device_create_command_encoder( + device_id, + &command_descriptor, + Some(encoder_id), + ); + if let Some(error) = error { + return Err(error.into()); + }; + let buffer_info = TexelCopyBufferInfo { + buffer: self.buffer_id, + layout: TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(config.stride()), + rows_per_image: None, + }, + }; + let texture_info = TexelCopyTextureInfo { + texture: texture_id, + mip_level: 0, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }; + let copy_size = Extent3d { + width: config.size.width, + height: config.size.height, + depth_or_array_layers: 1, + }; + self.global.command_encoder_copy_texture_to_buffer( + encoder_id, + &texture_info, + &buffer_info, + ©_size, + )?; + let (command_buffer_id, error) = self + .global + .command_encoder_finish(encoder_id, &CommandBufferDescriptor::default()); + if let Some(error) = error { + return Err(error.into()); + }; + Ok(command_buffer_id) + } + + /// Unmaps the buffer or cancels a mapping operation if one is in progress. + fn unmap(&mut self) { + match self.state { + StagingBufferState::Unassigned | StagingBufferState::Available(_) => {}, + StagingBufferState::Mapping(buffer) | + StagingBufferState::Mapped(MappedBuffer { buffer, .. }) => { + let _ = self.global.buffer_unmap(self.buffer_id); + self.state = StagingBufferState::Available(buffer) + }, + } + } + + /// Obtain a snapshot from this buffer if is mapped or return `None` if it is not mapped. + fn snapshot(&self) -> Option { + let StagingBufferState::Mapped(mapped) = &self.state else { + return None; + }; + let format = match mapped.image_format { + ImageFormat::RGBA8 => SnapshotPixelFormat::RGBA, + ImageFormat::BGRA8 => SnapshotPixelFormat::BGRA, + _ => unreachable!("GPUCanvasContext does not support other formats per spec"), + }; + let alpha_mode = if mapped.is_opaque { + SnapshotAlphaMode::AsOpaque { + premultiplied: false, + } + } else { + SnapshotAlphaMode::Transparent { + premultiplied: true, + } + }; + let padded_byte_width = mapped.stride(); + let data = mapped.slice(); + let bytes_per_pixel = mapped.image_format.bytes_per_pixel() as usize; + let mut result_unpadded = + Vec::::with_capacity(mapped.image_size.area() as usize * bytes_per_pixel); + for row in 0..mapped.image_size.height { + let start = (row * padded_byte_width).try_into().ok()?; + result_unpadded + .extend(&data[start..start + mapped.image_size.width as usize * bytes_per_pixel]); + } + let mut snapshot = + Snapshot::from_vec(mapped.image_size, format, alpha_mode, result_unpadded); + if mapped.is_opaque { + snapshot.transform(SnapshotAlphaMode::Opaque, snapshot.format()) + } + Some(snapshot) + } +} + +impl Drop for StagingBuffer { + fn drop(&mut self) { + match self.state { + StagingBufferState::Unassigned => {}, + StagingBufferState::Available(_) | + StagingBufferState::Mapping(_) | + StagingBufferState::Mapped(_) => { + self.global.buffer_drop(self.buffer_id); + }, + } + } +} + +#[derive(Default)] +pub struct WGPUExternalImages { + pub images: WGPUImageMap, + pub locked_ids: FxHashMap, +} + +impl WebrenderExternalImageApi for WGPUExternalImages { + fn lock(&mut self, id: u64) -> (ExternalImageSource<'_>, Size2D) { + let id = WebGPUContextId(id); + let presentation = { + let mut webgpu_contexts = self.images.lock().unwrap(); + webgpu_contexts + .get_mut(&id) + .and_then(|context_data| context_data.presentation.clone()) + }; + let Some(presentation) = presentation else { + return (ExternalImageSource::Invalid, Size2D::zero()); + }; + self.locked_ids.insert(id, presentation); + let presentation = self.locked_ids.get(&id).unwrap(); + let StagingBufferState::Mapped(mapped_buffer) = &presentation.staging_buffer.state else { + unreachable!("Presentation staging buffer should be mapped") + }; + let size = mapped_buffer.image_size; + ( + ExternalImageSource::RawData(mapped_buffer.slice()), + size.cast().cast_unit(), + ) + } + + fn unlock(&mut self, id: u64) { + let id = WebGPUContextId(id); + let Some(presentation) = self.locked_ids.remove(&id) else { + return; + }; + let mut webgpu_contexts = self.images.lock().unwrap(); + if let Some(context_data) = webgpu_contexts.get_mut(&id) { + // We use this to return staging buffer if a newer one exists. + presentation.maybe_destroy(context_data); + } else { + // This will not free this buffer id in script, + // but that's okay because we still have many free ids. + drop(presentation); + } + } +} + +/// Staging buffer currently used for presenting the epoch. +/// +/// Users should [`ContextData::replace_presentation`] when done. +#[derive(Clone)] +pub struct PresentationStagingBuffer { + epoch: Epoch, + staging_buffer: Arc, +} + +impl PresentationStagingBuffer { + fn new(epoch: Epoch, staging_buffer: StagingBuffer) -> Self { + Self { + epoch, + staging_buffer: Arc::new(staging_buffer), + } + } + + /// If the internal staging buffer is not shared, + /// unmap it and call [`ContextData::return_staging_buffer`] with it. + fn maybe_destroy(self, context_data: &mut ContextData) { + if let Some(mut staging_buffer) = Arc::into_inner(self.staging_buffer) { + staging_buffer.unmap(); + context_data.return_staging_buffer(staging_buffer); + } + } +} + +/// The embedder process-side representation of what is the `GPUCanvasContext` in script. +pub struct ContextData { + /// The [`ImageKey`] of the WebRender image associated with this context. + image_key: ImageKey, + /// Staging buffers that are not actively used. + /// + /// Staging buffer here are either [`StagingBufferState::Unassigned`] or [`StagingBufferState::Available`]. + /// They are removed from here when they are in process of being mapped or are already mapped. + inactive_staging_buffers: ArrayVec, + /// The [`PresentationStagingBuffer`] of the most recent presentation. This will + /// be `None` directly after initialization, as clearing is handled completely in + /// the `ScriptThread`. + presentation: Option, + /// Next epoch to be used + next_epoch: Epoch, +} + +impl ContextData { + fn new( + image_key: ImageKey, + global: &Arc, + buffer_ids: ArrayVec, + ) -> Self { + Self { + image_key, + inactive_staging_buffers: buffer_ids + .iter() + .map(|buffer_id| StagingBuffer::new(global.clone(), *buffer_id)) + .collect(), + presentation: None, + next_epoch: Epoch(1), + } + } + + /// Returns `None` if no staging buffer is unused or failure when making it available + fn get_or_make_available_buffer( + &'_ mut self, + config: &ContextConfiguration, + ) -> Option { + self.inactive_staging_buffers + .iter() + // Try to get first preallocated GPUBuffer. + .position(|staging_buffer| { + staging_buffer.is_available_and_has_compatible_config(config) + }) + // Fall back to the first inactive staging buffer. + .or_else(|| { + self.inactive_staging_buffers + .iter() + .position(|staging_buffer| staging_buffer.needs_assignment()) + }) + // Or just the use first one. + .or_else(|| { + if self.inactive_staging_buffers.is_empty() { + None + } else { + Some(0) + } + }) + .and_then(|index| { + let mut staging_buffer = self.inactive_staging_buffers.remove(index); + if staging_buffer.ensure_available(config).is_ok() { + Some(staging_buffer) + } else { + // If we fail to make it available, return it to the list of inactive staging buffers. + self.inactive_staging_buffers.push(staging_buffer); + None + } + }) + } + + /// Destroy the context that this [`ContextData`] represents, + /// freeing all of its buffers, and deleting the associated WebRender image. + fn destroy( + self, + script_sender: &IpcSender, + compositor_api: &CrossProcessCompositorApi, + ) { + // This frees the id in the `ScriptThread`. + for staging_buffer in self.inactive_staging_buffers { + if let Err(error) = script_sender.send(WebGPUMsg::FreeBuffer(staging_buffer.buffer_id)) + { + warn!( + "Unable to send FreeBuffer({:?}) ({error})", + staging_buffer.buffer_id + ); + }; + } + compositor_api.delete_image(self.image_key); + } + + /// Advance the [`Epoch`] and return the new one. + fn next_epoch(&mut self) -> Epoch { + let epoch = self.next_epoch; + self.next_epoch.next(); + epoch + } + + /// If the given [`PresentationStagingBuffer`] is for a newer presentation, replace the existing + /// one. Deallocate the older one by calling [`Self::return_staging_buffer`] on it. + fn replace_presentation(&mut self, presentation: PresentationStagingBuffer) { + let stale_presentation = if presentation.epoch >= + self.presentation + .as_ref() + .map(|p| p.epoch) + .unwrap_or(Epoch(0)) + { + self.presentation.replace(presentation) + } else { + Some(presentation) + }; + if let Some(stale_presentation) = stale_presentation { + stale_presentation.maybe_destroy(self); + } + } + + fn clear_presentation(&mut self) { + if let Some(stale_presentation) = self.presentation.take() { + stale_presentation.maybe_destroy(self); + } + } + + fn return_staging_buffer(&mut self, staging_buffer: StagingBuffer) { + self.inactive_staging_buffers.push(staging_buffer) + } +} + +impl crate::WGPU { + pub(crate) fn create_context( + &self, + context_id: WebGPUContextId, + image_key: ImageKey, + size: DeviceIntSize, + buffer_ids: ArrayVec, + ) { + let context_data = ContextData::new(image_key, &self.global, buffer_ids); + self.compositor_api.add_image( + image_key, + ImageDescriptor { + format: ImageFormat::BGRA8, + size, + stride: None, + offset: 0, + flags: ImageDescriptorFlags::empty(), + }, + SerializableImageData::External(image_data(context_id)), + ); + assert!( + self.wgpu_image_map + .lock() + .unwrap() + .insert(context_id, context_data) + .is_none(), + "Context should be created only once!" + ); + } + + pub(crate) fn get_image( + &self, + context_id: WebGPUContextId, + pending_texture: Option, + sender: IpcSender, + ) { + let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap(); + let context_data = webgpu_contexts.get_mut(&context_id).unwrap(); + if let Some(PendingTexture { + texture_id, + encoder_id, + configuration, + }) = pending_texture + { + let Some(staging_buffer) = context_data.get_or_make_available_buffer(&configuration) + else { + warn!("Failure obtaining available staging buffer"); + sender + .send(Snapshot::cleared(configuration.size).as_ipc()) + .unwrap(); + return; + }; + + let epoch = context_data.next_epoch(); + let wgpu_image_map = self.wgpu_image_map.clone(); + let sender = sender.clone(); + drop(webgpu_contexts); + self.texture_download( + texture_id, + encoder_id, + staging_buffer, + configuration, + move |staging_buffer| { + let mut webgpu_contexts = wgpu_image_map.lock().unwrap(); + let context_data = webgpu_contexts.get_mut(&context_id).unwrap(); + sender + .send( + staging_buffer + .snapshot() + .unwrap_or_else(|| Snapshot::cleared(configuration.size)) + .as_ipc(), + ) + .unwrap(); + if staging_buffer.is_mapped() { + context_data.replace_presentation(PresentationStagingBuffer::new( + epoch, + staging_buffer, + )); + } else { + // failure + context_data.return_staging_buffer(staging_buffer); + } + }, + ); + } else { + sender + .send( + context_data + .presentation + .as_ref() + .and_then(|presentation_staging_buffer| { + presentation_staging_buffer.staging_buffer.snapshot() + }) + .unwrap_or_else(Snapshot::empty) + .as_ipc(), + ) + .unwrap(); + } + } + + /// Read the texture to the staging buffer, map it to CPU memory, and update the + /// image in WebRender when complete. + pub(crate) fn present( + &self, + context_id: WebGPUContextId, + pending_texture: Option, + size: Size2D, + canvas_epoch: Epoch, + ) { + let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap(); + let context_data = webgpu_contexts.get_mut(&context_id).unwrap(); + let image_key = context_data.image_key; + let Some(PendingTexture { + texture_id, + encoder_id, + configuration, + }) = pending_texture + else { + context_data.clear_presentation(); + self.compositor_api.update_image( + image_key, + ImageDescriptor { + format: ImageFormat::BGRA8, + size: size.cast_unit().cast(), + stride: None, + offset: 0, + flags: ImageDescriptorFlags::empty(), + }, + SerializableImageData::External(image_data(context_id)), + Some(canvas_epoch), + ); + return; + }; + let Some(staging_buffer) = context_data.get_or_make_available_buffer(&configuration) else { + warn!("Failure obtaining available staging buffer"); + context_data.clear_presentation(); + self.compositor_api.update_image( + image_key, + configuration.into(), + SerializableImageData::External(image_data(context_id)), + Some(canvas_epoch), + ); + return; + }; + let epoch = context_data.next_epoch(); + let wgpu_image_map = self.wgpu_image_map.clone(); + let compositor_api = self.compositor_api.clone(); + drop(webgpu_contexts); + self.texture_download( + texture_id, + encoder_id, + staging_buffer, + configuration, + move |staging_buffer| { + let mut webgpu_contexts = wgpu_image_map.lock().unwrap(); + let context_data = webgpu_contexts.get_mut(&context_id).unwrap(); + if staging_buffer.is_mapped() { + context_data.replace_presentation(PresentationStagingBuffer::new( + epoch, + staging_buffer, + )); + } else { + context_data.return_staging_buffer(staging_buffer); + context_data.clear_presentation(); + } + // update image in WR + compositor_api.update_image( + image_key, + configuration.into(), + SerializableImageData::External(image_data(context_id)), + Some(canvas_epoch), + ); + }, + ); + } + + /// Copies data from provided texture using `encoder_id` to the provided [`StagingBuffer`]. + /// + /// `callback` is guaranteed to be called. + /// + /// Returns a [`StagingBuffer`] with the [`StagingBufferState::Mapped`] state + /// on success or [`StagingBufferState::Available`] on failure. + fn texture_download( + &self, + texture_id: TextureId, + encoder_id: CommandEncoderId, + mut staging_buffer: StagingBuffer, + config: ContextConfiguration, + callback: impl FnOnce(StagingBuffer) + Send + 'static, + ) { + let Ok(command_buffer_id) = + staging_buffer.prepare_load_texture_command_buffer(texture_id, encoder_id, &config) + else { + return callback(staging_buffer); + }; + let StagingBufferState::Available(buffer) = &staging_buffer.state else { + unreachable!("`prepare_load_texture_command_buffer` should make buffer available") + }; + let buffer_id = staging_buffer.buffer_id; + let buffer_size = buffer.size; + { + let _guard = self.poller.lock(); + let result = self + .global + .queue_submit(buffer.queue_id, &[command_buffer_id]); + if result.is_err() { + return callback(staging_buffer); + } + } + staging_buffer.state = match staging_buffer.state { + StagingBufferState::Available(buffer) => StagingBufferState::Mapping(buffer), + _ => unreachable!("`prepare_load_texture_command_buffer` should make buffer available"), + }; + let map_callback = { + let token = self.poller.token(); + Box::new(move |result: Result<(), BufferAccessError>| { + drop(token); + staging_buffer.state = match staging_buffer.state { + StagingBufferState::Mapping(buffer) => { + if let Ok((data, len)) = result.and_then(|_| { + staging_buffer.global.buffer_get_mapped_range( + staging_buffer.buffer_id, + 0, + Some(buffer.size), + ) + }) { + StagingBufferState::Mapped(MappedBuffer { + buffer, + data, + len, + image_size: config.size, + image_format: config.format, + is_opaque: config.is_opaque, + }) + } else { + StagingBufferState::Available(buffer) + } + }, + _ => { + unreachable!("Mapping buffer should have StagingBufferState::Mapping state") + }, + }; + callback(staging_buffer); + }) + }; + let map_op = BufferMapOperation { + host: HostMap::Read, + callback: Some(map_callback), + }; + // error is handled by map_callback + let _ = self + .global + .buffer_map_async(buffer_id, 0, Some(buffer_size), map_op); + self.poller.wake(); + } + + pub(crate) fn destroy_context(&mut self, context_id: WebGPUContextId) { + self.wgpu_image_map + .lock() + .unwrap() + .remove(&context_id) + .unwrap() + .destroy(&self.script_sender, &self.compositor_api); + } +} diff --git a/components/webgpu/lib.rs b/components/webgpu/lib.rs index eec9928df89..a73a65c0906 100644 --- a/components/webgpu/lib.rs +++ b/components/webgpu/lib.rs @@ -2,9 +2,9 @@ * 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/. */ +use canvas_context::WGPUImageMap; +pub use canvas_context::{ContextData, WGPUExternalImages}; use log::warn; -use swapchain::WGPUImageMap; -pub use swapchain::{ContextData, WGPUExternalImages}; use webgpu_traits::{WebGPU, WebGPUMsg}; use wgpu_thread::WGPU; pub use {wgpu_core as wgc, wgpu_types as wgt}; @@ -19,7 +19,7 @@ use compositing_traits::{CrossProcessCompositorApi, WebrenderExternalImageRegist use ipc_channel::ipc::{self, IpcReceiver}; use servo_config::pref; -pub mod swapchain; +pub mod canvas_context; pub fn start_webgpu_thread( compositor_api: CrossProcessCompositorApi, diff --git a/components/webgpu/swapchain.rs b/components/webgpu/swapchain.rs deleted file mode 100644 index 139134f1a59..00000000000 --- a/components/webgpu/swapchain.rs +++ /dev/null @@ -1,596 +0,0 @@ -/* 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/. */ - -use std::ptr::NonNull; -use std::slice; -use std::sync::{Arc, Mutex}; - -use arrayvec::ArrayVec; -use base::Epoch; -use compositing_traits::{ - CrossProcessCompositorApi, ExternalImageSource, SerializableImageData, - WebrenderExternalImageApi, -}; -use euclid::default::Size2D; -use ipc_channel::ipc::IpcSender; -use log::{error, warn}; -use pixels::{IpcSnapshot, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; -use rustc_hash::FxHashMap; -use serde::{Deserialize, Serialize}; -use webgpu_traits::{ - ContextConfiguration, Error, PRESENTATION_BUFFER_COUNT, WebGPUContextId, WebGPUMsg, -}; -use webrender_api::units::DeviceIntSize; -use webrender_api::{ - ExternalImageData, ExternalImageId, ExternalImageType, ImageDescriptor, ImageDescriptorFlags, - ImageFormat, ImageKey, -}; -use wgpu_core::device::HostMap; -use wgpu_core::global::Global; -use wgpu_core::id; -use wgpu_core::resource::{BufferAccessError, BufferMapOperation}; - -use crate::wgt; - -const DEFAULT_IMAGE_FORMAT: ImageFormat = ImageFormat::RGBA8; - -pub type WGPUImageMap = Arc>>; - -/// Presentation id encodes current configuration and current image -/// so that async presentation does not update context with older data -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] -struct PresentationId(u64); - -struct GPUPresentationBuffer { - global: Arc, - buffer_id: id::BufferId, - data: NonNull, - size: usize, -} - -// This is safe because `GPUPresentationBuffer` holds exclusive access to ptr -unsafe impl Send for GPUPresentationBuffer {} -unsafe impl Sync for GPUPresentationBuffer {} - -impl GPUPresentationBuffer { - fn new(global: Arc, buffer_id: id::BufferId, buffer_size: u64) -> Self { - let (data, size) = global - .buffer_get_mapped_range(buffer_id, 0, Some(buffer_size)) - .unwrap(); - GPUPresentationBuffer { - global, - buffer_id, - data, - size: size as usize, - } - } - - fn slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.data.as_ptr(), self.size) } - } -} - -impl Drop for GPUPresentationBuffer { - fn drop(&mut self) { - let _ = self.global.buffer_unmap(self.buffer_id); - } -} - -#[derive(Default)] -pub struct WGPUExternalImages { - pub images: WGPUImageMap, - pub locked_ids: FxHashMap>, -} - -impl WebrenderExternalImageApi for WGPUExternalImages { - fn lock(&mut self, id: u64) -> (ExternalImageSource<'_>, Size2D) { - let id = WebGPUContextId(id); - let webgpu_contexts = self.images.lock().unwrap(); - let context_data = webgpu_contexts.get(&id).unwrap(); - let size = context_data.image_desc.size().cast_unit(); - let data = if let Some(present_buffer) = context_data - .swap_chain - .as_ref() - .and_then(|swap_chain| swap_chain.data.as_ref()) - { - present_buffer.slice().to_vec() - } else { - context_data.dummy_data() - }; - self.locked_ids.insert(id, data); - ( - ExternalImageSource::RawData(self.locked_ids.get(&id).unwrap().as_slice()), - size, - ) - } - - fn unlock(&mut self, id: u64) { - let id = WebGPUContextId(id); - self.locked_ids.remove(&id); - } -} - -/// States of presentation buffer -#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] -enum PresentationBufferState { - /// Initial state, buffer has yet to be created, - /// only its id is reserved - #[default] - Unassigned, - /// Buffer is already created and ready to be used immediately - Available, - /// Buffer is currently running mapAsync - Mapping, - /// Buffer is currently actively mapped to be used by wr - Mapped, -} - -struct SwapChain { - device_id: id::DeviceId, - queue_id: id::QueueId, - data: Option, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct WebGPUImageDescriptor(pub ImageDescriptor); - -impl WebGPUImageDescriptor { - fn new(format: ImageFormat, size: DeviceIntSize, is_opaque: bool) -> Self { - let stride = ((size.width * format.bytes_per_pixel()) | - (wgt::COPY_BYTES_PER_ROW_ALIGNMENT as i32 - 1)) + - 1; - Self(ImageDescriptor { - format, - size, - stride: Some(stride), - offset: 0, - flags: if is_opaque { - ImageDescriptorFlags::IS_OPAQUE - } else { - ImageDescriptorFlags::empty() - }, - }) - } - - fn default(size: DeviceIntSize) -> Self { - Self::new(DEFAULT_IMAGE_FORMAT, size, false) - } - - /// Returns true if needs image update (if it's changed) - fn update(&mut self, new: Self) -> bool { - if self.0 != new.0 { - self.0 = new.0; - true - } else { - false - } - } - - fn buffer_stride(&self) -> i32 { - self.0 - .stride - .expect("Stride should be set by WebGPUImageDescriptor") - } - - fn buffer_size(&self) -> wgt::BufferAddress { - (self.buffer_stride() * self.0.size.height) as wgt::BufferAddress - } - - fn size(&self) -> DeviceIntSize { - self.0.size - } -} - -pub struct ContextData { - image_key: ImageKey, - image_desc: WebGPUImageDescriptor, - image_data: ExternalImageData, - buffer_ids: ArrayVec<(id::BufferId, PresentationBufferState), PRESENTATION_BUFFER_COUNT>, - /// If there is no associated swapchain the context is dummy (transparent black) - swap_chain: Option, - /// Next presentation id to be returned - next_presentation_id: PresentationId, - /// Current id that is presented/configured - /// - /// This value only grows - current_presentation_id: PresentationId, -} - -impl ContextData { - /// Init ContextData as dummy (transparent black) - fn new( - context_id: WebGPUContextId, - image_key: ImageKey, - size: DeviceIntSize, - buffer_ids: ArrayVec, - ) -> Self { - let image_data = ExternalImageData { - id: ExternalImageId(context_id.0), - channel_index: 0, - image_type: ExternalImageType::Buffer, - normalized_uvs: false, - }; - - Self { - image_key, - image_desc: WebGPUImageDescriptor::default(size), - image_data, - swap_chain: None, - buffer_ids: buffer_ids - .iter() - .map(|&buffer_id| (buffer_id, PresentationBufferState::Unassigned)) - .collect(), - current_presentation_id: PresentationId(0), - next_presentation_id: PresentationId(1), - } - } - - fn dummy_data(&self) -> Vec { - vec![0; self.image_desc.buffer_size() as usize] - } - - /// Returns id of available buffer - /// and sets state to PresentationBufferState::Mapping - fn get_available_buffer(&'_ mut self, global: &Arc) -> Option { - assert!(self.swap_chain.is_some()); - if let Some((buffer_id, buffer_state)) = self - .buffer_ids - .iter_mut() - .find(|(_, state)| *state == PresentationBufferState::Available) - { - *buffer_state = PresentationBufferState::Mapping; - Some(*buffer_id) - } else if let Some((buffer_id, buffer_state)) = self - .buffer_ids - .iter_mut() - .find(|(_, state)| *state == PresentationBufferState::Unassigned) - { - *buffer_state = PresentationBufferState::Mapping; - let buffer_id = *buffer_id; - let buffer_desc = wgt::BufferDescriptor { - label: None, - size: self.image_desc.buffer_size(), - usage: wgt::BufferUsages::MAP_READ | wgt::BufferUsages::COPY_DST, - mapped_at_creation: false, - }; - let _ = global.device_create_buffer( - self.swap_chain.as_ref().unwrap().device_id, - &buffer_desc, - Some(buffer_id), - ); - Some(buffer_id) - } else { - error!("No available presentation buffer: {:?}", self.buffer_ids); - None - } - } - - fn get_buffer_state(&mut self, buffer_id: id::BufferId) -> &mut PresentationBufferState { - &mut self - .buffer_ids - .iter_mut() - .find(|(id, _)| *id == buffer_id) - .expect("Presentation buffer should have associated state") - .1 - } - - fn unmap_old_buffer(&mut self, presentation_buffer: GPUPresentationBuffer) { - assert!(self.swap_chain.is_some()); - let buffer_state = self.get_buffer_state(presentation_buffer.buffer_id); - assert_eq!(*buffer_state, PresentationBufferState::Mapped); - *buffer_state = PresentationBufferState::Available; - drop(presentation_buffer); - } - - fn destroy_swapchain(&mut self, global: &Arc) { - drop(self.swap_chain.take()); - // free all buffers - for (buffer_id, buffer_state) in &mut self.buffer_ids { - match buffer_state { - PresentationBufferState::Unassigned => { - /* These buffer were not yet created in wgpu */ - }, - _ => { - global.buffer_drop(*buffer_id); - }, - } - *buffer_state = PresentationBufferState::Unassigned; - } - } - - fn destroy( - mut self, - global: &Arc, - script_sender: &IpcSender, - compositor_api: &CrossProcessCompositorApi, - ) { - self.destroy_swapchain(global); - for (buffer_id, _) in self.buffer_ids { - if let Err(e) = script_sender.send(WebGPUMsg::FreeBuffer(buffer_id)) { - warn!("Unable to send FreeBuffer({:?}) ({:?})", buffer_id, e); - }; - } - compositor_api.delete_image(self.image_key); - } - - /// Returns true if presentation id was updated (was newer) - fn check_and_update_presentation_id(&mut self, presentation_id: PresentationId) -> bool { - if presentation_id > self.current_presentation_id { - self.current_presentation_id = presentation_id; - true - } else { - false - } - } - - /// Returns new presentation id - fn next_presentation_id(&mut self) -> PresentationId { - let res = PresentationId(self.next_presentation_id.0); - self.next_presentation_id.0 += 1; - res - } -} - -impl crate::WGPU { - pub(crate) fn create_context( - &self, - context_id: WebGPUContextId, - image_key: ImageKey, - size: DeviceIntSize, - buffer_ids: ArrayVec, - ) { - let context_data = ContextData::new(context_id, image_key, size, buffer_ids); - self.compositor_api.add_image( - image_key, - context_data.image_desc.0, - SerializableImageData::External(context_data.image_data), - ); - assert!( - self.wgpu_image_map - .lock() - .unwrap() - .insert(context_id, context_data) - .is_none(), - "Context should be created only once!" - ); - } - - pub(crate) fn get_image(&self, context_id: WebGPUContextId) -> IpcSnapshot { - let webgpu_contexts = self.wgpu_image_map.lock().unwrap(); - let context_data = webgpu_contexts.get(&context_id).unwrap(); - let size = context_data.image_desc.size().cast().cast_unit(); - let data = if let Some(present_buffer) = context_data - .swap_chain - .as_ref() - .and_then(|swap_chain| swap_chain.data.as_ref()) - { - let format = match context_data.image_desc.0.format { - ImageFormat::RGBA8 => SnapshotPixelFormat::RGBA, - ImageFormat::BGRA8 => SnapshotPixelFormat::BGRA, - _ => unimplemented!(), - }; - let alpha_mode = if context_data.image_desc.0.is_opaque() { - SnapshotAlphaMode::AsOpaque { - premultiplied: false, - } - } else { - SnapshotAlphaMode::Transparent { - premultiplied: true, - } - }; - Snapshot::from_vec(size, format, alpha_mode, present_buffer.slice().to_vec()) - } else { - Snapshot::cleared(size) - }; - data.as_ipc() - } - - pub(crate) fn update_context( - &self, - context_id: WebGPUContextId, - size: DeviceIntSize, - config: Option, - ) { - let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap(); - let context_data = webgpu_contexts.get_mut(&context_id).unwrap(); - - let presentation_id = context_data.next_presentation_id(); - context_data.check_and_update_presentation_id(presentation_id); - - // If configuration is not provided - // the context will be dummy/empty until recreation - let needs_image_update = if let Some(config) = config { - let new_image_desc = - WebGPUImageDescriptor::new(config.format(), size, config.is_opaque); - let needs_swapchain_rebuild = context_data.swap_chain.is_none() || - new_image_desc.buffer_size() != context_data.image_desc.buffer_size(); - if needs_swapchain_rebuild { - context_data.destroy_swapchain(&self.global); - context_data.swap_chain = Some(SwapChain { - device_id: config.device_id, - queue_id: config.queue_id, - data: None, - }); - } - context_data.image_desc.update(new_image_desc) - } else { - context_data.destroy_swapchain(&self.global); - context_data - .image_desc - .update(WebGPUImageDescriptor::default(size)) - }; - - if needs_image_update { - self.compositor_api.update_image( - context_data.image_key, - context_data.image_desc.0, - SerializableImageData::External(context_data.image_data), - None, - ); - } - } - - /// Copies data async from provided texture using encoder_id to available staging presentation buffer - pub(crate) fn swapchain_present( - &mut self, - context_id: WebGPUContextId, - encoder_id: id::Id, - texture_id: id::Id, - canvas_epoch: Option, - ) -> Result<(), Box> { - fn err(e: Option) -> Result<(), T> { - if let Some(error) = e { - Err(error) - } else { - Ok(()) - } - } - - let global = &self.global; - let device_id; - let queue_id; - let buffer_id; - let image_desc; - let presentation_id; - { - if let Some(context_data) = self.wgpu_image_map.lock().unwrap().get_mut(&context_id) { - let Some(swap_chain) = context_data.swap_chain.as_ref() else { - return Ok(()); - }; - device_id = swap_chain.device_id; - queue_id = swap_chain.queue_id; - buffer_id = context_data.get_available_buffer(global).unwrap(); - image_desc = context_data.image_desc; - presentation_id = context_data.next_presentation_id(); - } else { - return Ok(()); - } - } - let comm_desc = wgt::CommandEncoderDescriptor { label: None }; - let (encoder_id, error) = - global.device_create_command_encoder(device_id, &comm_desc, Some(encoder_id)); - err(error)?; - let buffer_cv = wgt::TexelCopyBufferInfo { - buffer: buffer_id, - layout: wgt::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(image_desc.buffer_stride() as u32), - rows_per_image: None, - }, - }; - let texture_cv = wgt::TexelCopyTextureInfo { - texture: texture_id, - mip_level: 0, - origin: wgt::Origin3d::ZERO, - aspect: wgt::TextureAspect::All, - }; - let copy_size = wgt::Extent3d { - width: image_desc.size().width as u32, - height: image_desc.size().height as u32, - depth_or_array_layers: 1, - }; - global.command_encoder_copy_texture_to_buffer( - encoder_id, - &texture_cv, - &buffer_cv, - ©_size, - )?; - let (command_buffer_id, error) = - global.command_encoder_finish(encoder_id, &wgt::CommandBufferDescriptor::default()); - err(error)?; - { - let _guard = self.poller.lock(); - global - .queue_submit(queue_id, &[command_buffer_id]) - .map_err(|(_, error)| Error::from_error(error))?; - } - let callback = { - let global = Arc::clone(&self.global); - let wgpu_image_map = Arc::clone(&self.wgpu_image_map); - let compositor_api = self.compositor_api.clone(); - let token = self.poller.token(); - Box::new(move |result| { - drop(token); - update_wr_image( - result, - global, - buffer_id, - wgpu_image_map, - context_id, - compositor_api, - image_desc, - presentation_id, - canvas_epoch, - ); - }) - }; - let map_op = BufferMapOperation { - host: HostMap::Read, - callback: Some(callback), - }; - global.buffer_map_async(buffer_id, 0, Some(image_desc.buffer_size()), map_op)?; - self.poller.wake(); - Ok(()) - } - - pub(crate) fn destroy_context(&mut self, context_id: WebGPUContextId) { - self.wgpu_image_map - .lock() - .unwrap() - .remove(&context_id) - .unwrap() - .destroy(&self.global, &self.script_sender, &self.compositor_api); - } -} - -#[allow(clippy::too_many_arguments)] -fn update_wr_image( - result: Result<(), BufferAccessError>, - global: Arc, - buffer_id: id::BufferId, - wgpu_image_map: WGPUImageMap, - context_id: WebGPUContextId, - compositor_api: CrossProcessCompositorApi, - image_desc: WebGPUImageDescriptor, - presentation_id: PresentationId, - canvas_epoch: Option, -) { - match result { - Ok(()) => { - if let Some(context_data) = wgpu_image_map.lock().unwrap().get_mut(&context_id) { - if !context_data.check_and_update_presentation_id(presentation_id) { - let buffer_state = context_data.get_buffer_state(buffer_id); - if *buffer_state == PresentationBufferState::Mapping { - let _ = global.buffer_unmap(buffer_id); - *buffer_state = PresentationBufferState::Available; - } - // throw away all work, because we are too old - return; - } - assert_eq!(image_desc, context_data.image_desc); - let buffer_state = context_data.get_buffer_state(buffer_id); - assert_eq!(*buffer_state, PresentationBufferState::Mapping); - *buffer_state = PresentationBufferState::Mapped; - let presentation_buffer = - GPUPresentationBuffer::new(global, buffer_id, image_desc.buffer_size()); - let Some(swap_chain) = context_data.swap_chain.as_mut() else { - return; - }; - let old_presentation_buffer = swap_chain.data.replace(presentation_buffer); - compositor_api.update_image( - context_data.image_key, - context_data.image_desc.0, - SerializableImageData::External(context_data.image_data), - canvas_epoch, - ); - if let Some(old_presentation_buffer) = old_presentation_buffer { - context_data.unmap_old_buffer(old_presentation_buffer) - } - } else { - error!("WebGPU Context {:?} is destroyed", context_id); - } - }, - _ => error!("Could not map buffer({:?})", buffer_id), - } -} diff --git a/components/webgpu/wgpu_thread.rs b/components/webgpu/wgpu_thread.rs index d549f739fb0..046308c6158 100644 --- a/components/webgpu/wgpu_thread.rs +++ b/components/webgpu/wgpu_thread.rs @@ -36,8 +36,8 @@ use wgpu_types::MemoryHints; use wgt::InstanceDescriptor; pub use {wgpu_core as wgc, wgpu_types as wgt}; +use crate::canvas_context::WGPUImageMap; use crate::poll_thread::Poller; -use crate::swapchain::WGPUImageMap; #[derive(Eq, Hash, PartialEq)] pub(crate) struct DeviceScope { @@ -509,32 +509,19 @@ impl WGPU { }; self.create_context(context_id, image_key, size, buffer_ids); }, - WebGPURequest::UpdateContext { + WebGPURequest::Present { context_id, + pending_texture, size, - configuration, - } => { - self.update_context(context_id, size, configuration); - }, - WebGPURequest::SwapChainPresent { - context_id, - texture_id, - encoder_id, canvas_epoch, } => { - let result = self.swapchain_present( - context_id, - encoder_id, - texture_id, - canvas_epoch, - ); - if let Err(e) = result { - log::error!("Error occured in SwapChainPresent: {e:?}"); - } - }, - WebGPURequest::GetImage { context_id, sender } => { - sender.send(self.get_image(context_id)).unwrap() + self.present(context_id, pending_texture, size, canvas_epoch); }, + WebGPURequest::GetImage { + context_id, + pending_texture, + sender, + } => self.get_image(context_id, pending_texture, sender), WebGPURequest::ValidateTextureDescriptor { device_id, texture_id, @@ -1185,25 +1172,22 @@ impl WGPU { let device_scope = devices .get_mut(&device_id) .expect("Device should not be dropped by this point"); - if let Some(error_scope_stack) = &mut device_scope.error_scope_stack { - if let Some(error_scope) = error_scope_stack.pop() { - if let Err(e) = sender.send(Ok( - // TODO: Do actual selection instead of selecting first error - error_scope.errors.first().cloned(), - )) { - warn!( - "Unable to send {:?} to poperrorscope: {e:?}", - error_scope.errors - ); + let result = + if let Some(error_scope_stack) = &mut device_scope.error_scope_stack { + if let Some(error_scope) = error_scope_stack.pop() { + Ok( + // TODO: Do actual selection instead of selecting first error + error_scope.errors.first().cloned(), + ) + } else { + Err(PopError::Empty) } - } else if let Err(e) = sender.send(Err(PopError::Empty)) { - warn!("Unable to send PopError::Empty: {e:?}"); - } - } else { - // device lost - if let Err(e) = sender.send(Err(PopError::Lost)) { - warn!("Unable to send PopError::Lost due {e:?}"); - } + } else { + // This means the device has been lost. + Err(PopError::Lost) + }; + if let Err(error) = sender.send(result) { + warn!("Error while sending PopErrorScope result: {error}"); } }, WebGPURequest::ComputeGetBindGroupLayout { diff --git a/tests/wpt/webgpu/meta/webgpu/cts.https.html.ini b/tests/wpt/webgpu/meta/webgpu/cts.https.html.ini index 1ef0696567f..999495cd1a1 100644 --- a/tests/wpt/webgpu/meta/webgpu/cts.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/cts.https.html.ini @@ -80659,8 +80659,12 @@ if os == "linux" and not debug: FAIL [:canvasType="onscreen";contextType="bitmaprenderer";awaitLost=false] + expected: + if os == "linux" and not debug: FAIL [:canvasType="onscreen";contextType="bitmaprenderer";awaitLost=true] + expected: + if os == "linux" and not debug: FAIL [:canvasType="onscreen";contextType="webgl";awaitLost=false] expected: @@ -547197,8 +547201,12 @@ [cts.https.html?q=webgpu:web_platform,canvas,getCurrentTexture:multiple_frames:*] [:canvasType="offscreen"] + expected: + if os == "linux" and not debug: FAIL [:canvasType="onscreen"] + expected: + if os == "linux" and not debug: FAIL [cts.https.html?q=webgpu:web_platform,canvas,getCurrentTexture:resize:*] @@ -547575,16 +547583,10 @@ if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="opaque";colorSpace="srgb";snapshotType="imageBitmap"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="opaque";colorSpace="srgb";snapshotType="toBlob"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="opaque";colorSpace="srgb";snapshotType="toDataURL"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";colorSpace="display-p3";snapshotType="imageBitmap"] expected: @@ -547599,16 +547601,10 @@ if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";colorSpace="srgb";snapshotType="imageBitmap"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";colorSpace="srgb";snapshotType="toBlob"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";colorSpace="srgb";snapshotType="toDataURL"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba16float";alphaMode="opaque";colorSpace="display-p3";snapshotType="imageBitmap"] expected: @@ -547671,16 +547667,10 @@ if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";colorSpace="srgb";snapshotType="imageBitmap"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";colorSpace="srgb";snapshotType="toBlob"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";colorSpace="srgb";snapshotType="toDataURL"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";colorSpace="display-p3";snapshotType="imageBitmap"] expected: @@ -547695,50 +547685,28 @@ if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";colorSpace="srgb";snapshotType="imageBitmap"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";colorSpace="srgb";snapshotType="toBlob"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";colorSpace="srgb";snapshotType="toDataURL"] - expected: - if os == "linux" and not debug: FAIL [cts.https.html?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,uploadToWebGL:*] [:format="bgra8unorm";alphaMode="opaque";webgl="webgl";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="opaque";webgl="webgl";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="opaque";webgl="webgl2";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="opaque";webgl="webgl2";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";webgl="webgl";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";webgl="webgl";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";webgl="webgl2";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="bgra8unorm";alphaMode="premultiplied";webgl="webgl2";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba16float";alphaMode="opaque";webgl="webgl";upload="texImage2D"] expected: @@ -547773,36 +547741,20 @@ if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";webgl="webgl";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";webgl="webgl";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";webgl="webgl2";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="opaque";webgl="webgl2";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";webgl="webgl";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";webgl="webgl";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";webgl="webgl2";upload="texImage2D"] - expected: - if os == "linux" and not debug: FAIL [:format="rgba8unorm";alphaMode="premultiplied";webgl="webgl2";upload="texSubImage2D"] - expected: - if os == "linux" and not debug: FAIL [cts.https.html?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_huge_size:*] diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html.ini index 4919300ef34..5d0543c4036 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html.ini @@ -1,6 +1,3 @@ [canvas_colorspace_bgra8unorm.https.html] expected: - if os == "win": TIMEOUT - if os == "linux" and debug: TIMEOUT if os == "linux" and not debug: FAIL - if os == "mac": TIMEOUT diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html.ini new file mode 100644 index 00000000000..dd38fd7a4c2 --- /dev/null +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html.ini @@ -0,0 +1,3 @@ +[canvas_colorspace_rgba16float.https.html] + expected: + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html.ini index 0514a190bb4..068fadc7b9e 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html.ini @@ -1,6 +1,3 @@ [canvas_colorspace_rgba8unorm.https.html] expected: - if os == "win": TIMEOUT - if os == "linux" and debug: TIMEOUT if os == "linux" and not debug: FAIL - if os == "mac": TIMEOUT diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html.ini index 7a10a5aab39..7153b10f0ba 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html.ini @@ -1,6 +1,3 @@ [canvas_complex_bgra8unorm_copy.https.html] expected: - if os == "win": TIMEOUT - if os == "linux" and debug: TIMEOUT if os == "linux" and not debug: FAIL - if os == "mac": TIMEOUT diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html.ini index ff7a9278d6d..6a5bc6d50ad 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html.ini @@ -1,6 +1,3 @@ [canvas_complex_bgra8unorm_draw.https.html] expected: - if os == "win": TIMEOUT - if os == "linux" and debug: TIMEOUT if os == "linux" and not debug: FAIL - if os == "mac": TIMEOUT diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html.ini index aad3457d0ba..5581f42f920 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html.ini @@ -1,6 +1,3 @@ [canvas_complex_rgba8unorm_draw.https.html] expected: - if os == "win": TIMEOUT - if os == "linux" and debug: TIMEOUT if os == "linux" and not debug: FAIL - if os == "mac": TIMEOUT diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html.ini index f58c35f5777..3848337c5b5 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html.ini @@ -1,3 +1,3 @@ [canvas_complex_rgba8unorm_store.https.html] expected: - if os == "linux" and not debug: [PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html.ini index 6e592d5d2f6..51d6acd04b6 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_bgra8unorm_opaque_copy.https.html] expected: - if os == "linux" and not debug: [CRASH, PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html.ini index 8e19b1c1250..ea18ac0dc79 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html.ini @@ -1,6 +1,3 @@ [canvas_composite_alpha_bgra8unorm_opaque_draw.https.html] expected: - if os == "win": PASS - if os == "linux" and debug: PASS - if os == "linux" and not debug: [PASS, FAIL] - if os == "mac": PASS + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html.ini index 214752c8ffc..3a56987d61d 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html] expected: - if os == "linux" and not debug: [PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html.ini index 75c738ae1f6..db825da0175 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html] expected: - if os == "linux" and not debug: [PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html.ini index 34ca7bdbd92..4e3391e6669 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_rgba8unorm_opaque_copy.https.html] expected: - if os == "linux" and not debug: [PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html.ini index 3684ca84a50..5c94c840149 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_rgba8unorm_opaque_draw.https.html] expected: - if os == "linux" and not debug: [PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html.ini index 38fc5fe0c46..87632985610 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html] expected: - if os == "linux" and not debug: [CRASH, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html.ini index 680ef6a014b..7c5b6119847 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html.ini @@ -1,3 +1,3 @@ [canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html] expected: - if os == "linux" and not debug: [CRASH, PASS, FAIL] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html.ini index 0f8b1a56a6e..1582f163f6d 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html.ini @@ -1,3 +1,3 @@ [canvas_image_rendering.https.html] expected: - if os == "linux" and not debug: [CRASH, PASS] + if os == "linux" and not debug: PASS diff --git a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/resize_observer.https.html.ini b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/resize_observer.https.html.ini index 259fb223468..d25952f7326 100644 --- a/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/resize_observer.https.html.ini +++ b/tests/wpt/webgpu/meta/webgpu/webgpu/web_platform/reftests/resize_observer.https.html.ini @@ -1,2 +1,3 @@ [resize_observer.https.html] - expected: TIMEOUT + expected: + if os == "linux" and not debug: TIMEOUT