diff options
author | Samson <16504129+sagudev@users.noreply.github.com> | 2025-02-21 21:26:27 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-21 20:26:27 +0000 |
commit | a6f19c0092de2e86f4d79c8328e66589764b6da6 (patch) | |
tree | a6afbf8fdb371b20ce6fc9a811b220329c5ebdd7 /components | |
parent | 084006abb627da8e1f0920887d68c3fba01adf72 (diff) | |
download | servo-a6f19c0092de2e86f4d79c8328e66589764b6da6.tar.gz servo-a6f19c0092de2e86f4d79c8328e66589764b6da6.zip |
script: Add `CanvasContext` trait (#35448)
* trait `CanvasContext`
Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
* fixup most stuff
Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
* explain and limit crown `allow(crown::unrooted_must_root)`
Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
---------
Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
Diffstat (limited to 'components')
-rw-r--r-- | components/script/canvas_context.rs | 77 | ||||
-rw-r--r-- | components/script/dom/canvasrenderingcontext2d.rs | 69 | ||||
-rw-r--r-- | components/script/dom/document.rs | 3 | ||||
-rw-r--r-- | components/script/dom/htmlcanvaselement.rs | 48 | ||||
-rw-r--r-- | components/script/dom/webgl2renderingcontext.rs | 38 | ||||
-rw-r--r-- | components/script/dom/webglprogram.rs | 1 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 228 | ||||
-rw-r--r-- | components/script/dom/webgpu/gpucanvascontext.rs | 59 | ||||
-rw-r--r-- | components/script/dom/webxr/xrlayer.rs | 1 | ||||
-rw-r--r-- | components/script/dom/webxr/xrsession.rs | 1 | ||||
-rw-r--r-- | components/script/dom/webxr/xrwebgllayer.rs | 1 | ||||
-rw-r--r-- | components/script/lib.rs | 1 |
12 files changed, 317 insertions, 210 deletions
diff --git a/components/script/canvas_context.rs b/components/script/canvas_context.rs new file mode 100644 index 00000000000..74855781805 --- /dev/null +++ b/components/script/canvas_context.rs @@ -0,0 +1,77 @@ +/* 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/. */ + +//! Common interfaces for Canvas Contexts + +use canvas_traits::canvas::CanvasId; +use euclid::default::Size2D; +use ipc_channel::ipc::IpcSharedMemory; +use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource}; + +use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::node::{Node, NodeDamage}; + +pub(crate) trait LayoutCanvasRenderingContextHelpers { + fn canvas_data_source(self) -> HTMLCanvasDataSource; +} + +pub(crate) trait LayoutHTMLCanvasElementHelpers { + fn data(self) -> HTMLCanvasData; + fn get_canvas_id_for_layout(self) -> CanvasId; +} + +pub(crate) trait CanvasContext { + type ID; + + fn context_id(&self) -> Self::ID; + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas; + + fn resize(&self); + + fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory>; + + fn get_image_data(&self) -> Option<Vec<u8>> { + self.get_image_data_as_shared_memory().map(|sm| sm.to_vec()) + } + + fn origin_is_clean(&self) -> bool { + true + } + + fn size(&self) -> Size2D<u64> { + self.canvas().size() + } + + fn mark_as_dirty(&self) { + if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) = &self.canvas() { + canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + } + + fn update_rendering(&self) {} + + fn onscreen(&self) -> bool { + match self.canvas() { + HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => { + canvas.upcast::<Node>().is_connected() + }, + // FIXME(34628): Offscreen canvases should be considered offscreen if a placeholder is set. + // <https://www.w3.org/TR/webgpu/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas> + HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => false, + } + } +} + +impl HTMLCanvasElementOrOffscreenCanvas { + pub(crate) fn size(&self) -> Size2D<u64> { + match self { + HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => { + canvas.get_size().cast() + }, + HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(), + } + } +} diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 307cead72ba..3ba0addfb17 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -11,12 +11,15 @@ use ipc_channel::ipc::{IpcSender, IpcSharedMemory}; use profile_traits::ipc; use servo_url::ServoUrl; +use crate::canvas_context::CanvasContext; use crate::canvas_state::CanvasState; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{ CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin, CanvasRenderingContext2DMethods, CanvasTextAlign, CanvasTextBaseline, }; -use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; +use crate::dom::bindings::codegen::UnionTypes::{ + HTMLCanvasElementOrOffscreenCanvas, StringOrCanvasGradientOrCanvasPattern, +}; use crate::dom::bindings::error::{ErrorResult, Fallible}; use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector}; @@ -92,10 +95,6 @@ impl CanvasRenderingContext2D { self.canvas_state.set_bitmap_dimensions(size); } - pub(crate) fn mark_as_dirty(&self) { - self.canvas_state.mark_as_dirty(self.canvas.as_deref()) - } - pub(crate) fn take_missing_image_urls(&self) -> Vec<ServoUrl> { std::mem::take(&mut self.canvas_state.get_missing_image_urls().borrow_mut()) } @@ -108,10 +107,6 @@ impl CanvasRenderingContext2D { self.canvas_state.send_canvas_2d_msg(msg) } - pub(crate) fn origin_is_clean(&self) -> bool { - self.canvas_state.origin_is_clean() - } - pub(crate) fn get_rect(&self, rect: Rect<u32>) -> Vec<u8> { let rect = Rect::new( Point2D::new(rect.origin.x as u64, rect.origin.y as u64), @@ -125,14 +120,6 @@ impl CanvasRenderingContext2D { ) } - pub(crate) fn fetch_data(&self) -> IpcSharedMemory { - let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); - let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender), self.get_canvas_id()); - self.canvas_state.get_ipc_renderer().send(msg).unwrap(); - - receiver.recv().unwrap() - } - pub(crate) fn send_data(&self, sender: IpcSender<CanvasImageData>) { let msg = CanvasMsg::FromLayout(FromLayoutMsg::SendData(sender), self.get_canvas_id()); let _ = self.canvas_state.get_ipc_renderer().send(msg); @@ -157,6 +144,54 @@ impl LayoutCanvasRenderingContext2DHelpers for LayoutDom<'_, CanvasRenderingCont } } +impl CanvasContext for CanvasRenderingContext2D { + type ID = CanvasId; + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Crown is wrong here #35570 + fn context_id(&self) -> Self::ID { + self.canvas_state.get_canvas_id() + } + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + if let Some(ref canvas) = self.canvas { + HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas.as_rooted()) + } else { + unimplemented!() + } + } + + fn resize(&self) { + self.set_bitmap_dimensions(self.size().cast()) + } + + fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> { + let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); + let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender), self.get_canvas_id()); + self.canvas_state.get_ipc_renderer().send(msg).unwrap(); + + Some(receiver.recv().unwrap()) + } + + fn get_image_data(&self) -> Option<Vec<u8>> { + Some(self.get_rect(Rect::from_size(self.size().cast()))) + } + + fn origin_is_clean(&self) -> bool { + self.canvas_state.origin_is_clean() + } + + fn mark_as_dirty(&self) { + self.canvas_state.mark_as_dirty(self.canvas.as_deref()) + } + + fn size(&self) -> Size2D<u64> { + self.canvas + .as_ref() + .map(|c| c.get_size().cast()) + .unwrap_or(Size2D::zero()) + } +} + // We add a guard to each of methods by the spec: // http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/ // diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index ea4ac90422c..ac8c86cfb71 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -80,6 +80,7 @@ use super::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMet use super::clipboardevent::ClipboardEventType; use crate::animation_timeline::AnimationTimeline; use crate::animations::Animations; +use crate::canvas_context::CanvasContext as _; use crate::document_loader::{DocumentLoader, LoadType}; use crate::dom::attr::Attr; use crate::dom::beforeunloadevent::BeforeUnloadEvent; @@ -3326,7 +3327,7 @@ impl Document { .iter() .filter_map(|(_, context)| context.root()) .filter(|context| context.onscreen()) - .for_each(|context| context.update_rendering_of_webgpu_canvas()); + .for_each(|context| context.update_rendering()); } pub(crate) fn id_map(&self) -> Ref<HashMapTracedValues<Atom, Vec<Dom<Element>>>> { diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 112fc26f8ae..4e36395707a 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -9,7 +9,7 @@ use std::rc::Rc; use canvas_traits::canvas::{CanvasId, CanvasMsg, FromScriptMsg}; use canvas_traits::webgl::{GLContextAttributes, WebGLVersion}; use dom_struct::dom_struct; -use euclid::default::{Rect, Size2D}; +use euclid::default::Size2D; use html5ever::{local_name, namespace_url, ns, LocalName, Prefix}; use image::codecs::jpeg::JpegEncoder; use image::codecs::png::PngEncoder; @@ -29,6 +29,8 @@ use servo_media::streams::registry::MediaStreamId; use servo_media::streams::MediaStreamType; use style::attr::AttrValue; +use crate::canvas_context::CanvasContext as _; +pub(crate) use crate::canvas_context::*; use crate::dom::attr::Attr; use crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref}; use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{ @@ -158,19 +160,16 @@ impl HTMLCanvasElement { ) } - fn recreate_contexts(&self) { - let size = self.get_size(); + fn recreate_contexts_after_resize(&self) { if let Some(ref context) = *self.context.borrow() { match *context { - CanvasContext::Context2d(ref context) => { - context.set_canvas_bitmap_dimensions(size.to_u64()) - }, - CanvasContext::WebGL(ref context) => context.recreate(size), - CanvasContext::WebGL2(ref context) => context.recreate(size), + CanvasContext::Context2d(ref context) => context.resize(), + CanvasContext::WebGL(ref context) => context.resize(), + CanvasContext::WebGL2(ref context) => context.resize(), #[cfg(feature = "webgpu")] CanvasContext::WebGPU(ref context) => context.resize(), CanvasContext::Placeholder(ref context) => { - context.set_canvas_bitmap_dimensions(size.to_u64()) + context.set_canvas_bitmap_dimensions(self.get_size().to_u64()) }, } } @@ -208,15 +207,6 @@ impl HTMLCanvasElement { } } -pub(crate) trait LayoutCanvasRenderingContextHelpers { - fn canvas_data_source(self) -> HTMLCanvasDataSource; -} - -pub(crate) trait LayoutHTMLCanvasElementHelpers { - fn data(self) -> HTMLCanvasData; - fn get_canvas_id_for_layout(self) -> CanvasId; -} - impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { #[allow(unsafe_code)] fn data(self) -> HTMLCanvasData { @@ -401,17 +391,17 @@ impl HTMLCanvasElement { } let data = match self.context.borrow().as_ref() { - Some(CanvasContext::Context2d(context)) => Some(context.fetch_data()), - Some(&CanvasContext::WebGL(_)) => { + Some(CanvasContext::Context2d(context)) => context.get_image_data_as_shared_memory(), + Some(CanvasContext::WebGL(_context)) => { // TODO: add a method in WebGLRenderingContext to get the pixels. return None; }, - Some(&CanvasContext::WebGL2(_)) => { + Some(CanvasContext::WebGL2(_context)) => { // TODO: add a method in WebGL2RenderingContext to get the pixels. return None; }, #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(context)) => Some(context.get_ipc_image()), + Some(CanvasContext::WebGPU(context)) => context.get_image_data_as_shared_memory(), Some(CanvasContext::Placeholder(context)) => { let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); @@ -431,15 +421,11 @@ impl HTMLCanvasElement { fn get_content(&self) -> Option<Vec<u8>> { match *self.context.borrow() { - Some(CanvasContext::Context2d(ref context)) => { - Some(context.get_rect(Rect::from_size(self.get_size()))) - }, - Some(CanvasContext::WebGL(ref context)) => context.get_image_data(self.get_size()), - Some(CanvasContext::WebGL2(ref context)) => { - context.base_context().get_image_data(self.get_size()) - }, + Some(CanvasContext::Context2d(ref context)) => context.get_image_data(), + Some(CanvasContext::WebGL(ref context)) => context.get_image_data(), + Some(CanvasContext::WebGL2(ref context)) => context.get_image_data(), #[cfg(feature = "webgpu")] - Some(CanvasContext::WebGPU(ref context)) => Some(context.get_image_data()), + Some(CanvasContext::WebGPU(ref context)) => context.get_image_data(), Some(CanvasContext::Placeholder(_)) | None => { // Each pixel is fully-transparent black. Some(vec![0; (self.Width() * self.Height() * 4) as usize]) @@ -737,7 +723,7 @@ impl VirtualMethods for HTMLCanvasElement { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { - &local_name!("width") | &local_name!("height") => self.recreate_contexts(), + &local_name!("width") | &local_name!("height") => self.recreate_contexts_after_resize(), _ => (), }; } diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs index b69d0f6961a..c7cc25526d5 100644 --- a/components/script/dom/webgl2renderingcontext.rs +++ b/components/script/dom/webgl2renderingcontext.rs @@ -11,8 +11,8 @@ use std::rc::Rc; use bitflags::bitflags; use canvas_traits::webgl::WebGLError::*; use canvas_traits::webgl::{ - webgl_channel, GLContextAttributes, InternalFormatParameter, WebGLCommand, WebGLResult, - WebGLVersion, + webgl_channel, GLContextAttributes, InternalFormatParameter, WebGLCommand, WebGLContextId, + WebGLResult, WebGLVersion, }; use dom_struct::dom_struct; use euclid::default::{Point2D, Rect, Size2D}; @@ -25,6 +25,7 @@ use script_layout_interface::HTMLCanvasDataSource; use servo_config::pref; use url::Host; +use crate::canvas_context::CanvasContext; use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::{ WebGL2RenderingContextConstants as constants, WebGL2RenderingContextMethods, }; @@ -205,10 +206,6 @@ impl WebGL2RenderingContext { static WEBGL2_ORIGINS: &[&str] = &["www.servoexperiments.com"]; impl WebGL2RenderingContext { - pub(crate) fn recreate(&self, size: Size2D<u32>) { - self.base.recreate(size) - } - pub(crate) fn current_vao(&self) -> DomRoot<WebGLVertexArrayObject> { self.base.current_vao_webgl2() } @@ -903,6 +900,35 @@ impl WebGL2RenderingContext { } } +impl CanvasContext for WebGL2RenderingContext { + type ID = WebGLContextId; + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Crown is wrong here #35570 + fn context_id(&self) -> Self::ID { + self.base.context_id() + } + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + self.base.canvas().clone() + } + + fn resize(&self) { + self.base.resize(); + } + + fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> { + self.base.get_image_data_as_shared_memory() + } + + fn get_image_data(&self) -> Option<Vec<u8>> { + self.base.get_image_data() + } + + fn mark_as_dirty(&self) { + self.base.mark_as_dirty() + } +} + impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingContext { /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1> fn Canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { diff --git a/components/script/dom/webglprogram.rs b/components/script/dom/webglprogram.rs index 674be65fa7f..ad0edb4c64b 100644 --- a/components/script/dom/webglprogram.rs +++ b/components/script/dom/webglprogram.rs @@ -12,6 +12,7 @@ use canvas_traits::webgl::{ use dom_struct::dom_struct; use fnv::FnvHashSet; +use crate::canvas_context::CanvasContext; use crate::dom::bindings::cell::{DomRefCell, Ref}; use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants2; use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 8e3fa40839d..98a60a19169 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -36,6 +36,7 @@ use serde::{Deserialize, Serialize}; use servo_config::pref; use webrender_api::ImageKey; +use crate::canvas_context::CanvasContext; use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut}; use crate::dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants; use crate::dom::bindings::codegen::Bindings::EXTBlendMinmaxBinding::EXTBlendMinmaxConstants; @@ -351,68 +352,6 @@ impl WebGLRenderingContext { self.current_vertex_attribs.borrow_mut() } - pub(crate) fn recreate(&self, size: Size2D<u32>) { - let (sender, receiver) = webgl_channel().unwrap(); - self.webgl_sender.send_resize(size, sender).unwrap(); - // FIXME(#21718) The backend is allowed to choose a size smaller than - // what was requested - self.size.set(size); - - if let Err(msg) = receiver.recv().unwrap() { - error!("Error resizing WebGLContext: {}", msg); - return; - }; - - // ClearColor needs to be restored because after a resize the GLContext is recreated - // and the framebuffer is cleared using the default black transparent color. - let color = self.current_clear_color.get(); - self.send_command(WebGLCommand::ClearColor(color.0, color.1, color.2, color.3)); - - // WebGL Spec: Scissor rect must not change if the canvas is resized. - // See: webgl/conformance-1.0.3/conformance/rendering/gl-scissor-canvas-dimensions.html - // NativeContext handling library changes the scissor after a resize, so we need to reset the - // default scissor when the canvas was created or the last scissor that the user set. - let rect = self.current_scissor.get(); - self.send_command(WebGLCommand::Scissor(rect.0, rect.1, rect.2, rect.3)); - - // Bound texture must not change when the canvas is resized. - // Right now surfman generates a new FBO and the bound texture is changed - // in order to create a new render to texture attachment. - // Send a command to re-bind the TEXTURE_2D, if any. - if let Some(texture) = self - .textures - .active_texture_slot(constants::TEXTURE_2D, self.webgl_version()) - .unwrap() - .get() - { - self.send_command(WebGLCommand::BindTexture( - constants::TEXTURE_2D, - Some(texture.id()), - )); - } - - // Bound framebuffer must not change when the canvas is resized. - // Right now surfman generates a new FBO on resize. - // Send a command to re-bind the framebuffer, if any. - if let Some(fbo) = self.bound_draw_framebuffer.get() { - let id = WebGLFramebufferBindingRequest::Explicit(fbo.id()); - self.send_command(WebGLCommand::BindFramebuffer(constants::FRAMEBUFFER, id)); - } - } - - pub(crate) fn context_id(&self) -> WebGLContextId { - self.webgl_sender.context_id() - } - - pub(crate) fn onscreen(&self) -> bool { - match self.canvas { - HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => { - canvas.upcast::<Node>().is_connected() - }, - HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => false, - } - } - #[inline] pub(crate) fn send_command(&self, command: WebGLCommand) { self.webgl_sender @@ -538,27 +477,6 @@ impl WebGLRenderingContext { } } - pub(crate) fn mark_as_dirty(&self) { - // If we have a bound framebuffer, then don't mark the canvas as dirty. - if self.bound_draw_framebuffer.get().is_some() { - return; - } - - // Dirtying the canvas is unnecessary if we're actively displaying immersive - // XR content right now. - if self.global().as_window().in_immersive_xr_session() { - return; - } - - match self.canvas { - HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => { - canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - canvas.owner_document().add_dirty_webgl_canvas(self); - }, - HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => {}, - } - } - fn vertex_attrib(&self, indx: u32, x: f32, y: f32, z: f32, w: f32) { if indx >= self.limits.max_vertex_attribs { return self.webgl_error(InvalidValue); @@ -1133,33 +1051,6 @@ impl WebGLRenderingContext { self.send_command(WebGLCommand::VertexAttribDivisor { index, divisor }); } - // Used by HTMLCanvasElement.toDataURL - // - // This emits errors quite liberally, but the spec says that this operation - // can fail and that it is UB what happens in that case. - // - // https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2 - pub(crate) fn get_image_data(&self, mut size: Size2D<u32>) -> Option<Vec<u8>> { - handle_potential_webgl_error!(self, self.validate_framebuffer(), return None); - - let (fb_width, fb_height) = handle_potential_webgl_error!( - self, - self.get_current_framebuffer_size().ok_or(InvalidOperation), - return None - ); - size.width = cmp::min(size.width, fb_width as u32); - size.height = cmp::min(size.height, fb_height as u32); - - let (sender, receiver) = ipc::bytes_channel().unwrap(); - self.send_command(WebGLCommand::ReadPixels( - Rect::from_size(size), - constants::RGBA, - constants::UNSIGNED_BYTE, - sender, - )); - Some(receiver.recv().unwrap()) - } - pub(crate) fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> { self.bound_buffer_array.get() } @@ -1970,6 +1861,123 @@ impl WebGLRenderingContext { } } +impl CanvasContext for WebGLRenderingContext { + type ID = WebGLContextId; + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Crown is wrong here #35570 + fn context_id(&self) -> Self::ID { + self.webgl_sender.context_id() + } + + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + self.canvas.clone() + } + + fn resize(&self) { + let size = self.size().cast(); + let (sender, receiver) = webgl_channel().unwrap(); + self.webgl_sender.send_resize(size, sender).unwrap(); + // FIXME(#21718) The backend is allowed to choose a size smaller than + // what was requested + self.size.set(size); + + if let Err(msg) = receiver.recv().unwrap() { + error!("Error resizing WebGLContext: {}", msg); + return; + }; + + // ClearColor needs to be restored because after a resize the GLContext is recreated + // and the framebuffer is cleared using the default black transparent color. + let color = self.current_clear_color.get(); + self.send_command(WebGLCommand::ClearColor(color.0, color.1, color.2, color.3)); + + // WebGL Spec: Scissor rect must not change if the canvas is resized. + // See: webgl/conformance-1.0.3/conformance/rendering/gl-scissor-canvas-dimensions.html + // NativeContext handling library changes the scissor after a resize, so we need to reset the + // default scissor when the canvas was created or the last scissor that the user set. + let rect = self.current_scissor.get(); + self.send_command(WebGLCommand::Scissor(rect.0, rect.1, rect.2, rect.3)); + + // Bound texture must not change when the canvas is resized. + // Right now surfman generates a new FBO and the bound texture is changed + // in order to create a new render to texture attachment. + // Send a command to re-bind the TEXTURE_2D, if any. + if let Some(texture) = self + .textures + .active_texture_slot(constants::TEXTURE_2D, self.webgl_version()) + .unwrap() + .get() + { + self.send_command(WebGLCommand::BindTexture( + constants::TEXTURE_2D, + Some(texture.id()), + )); + } + + // Bound framebuffer must not change when the canvas is resized. + // Right now surfman generates a new FBO on resize. + // Send a command to re-bind the framebuffer, if any. + if let Some(fbo) = self.bound_draw_framebuffer.get() { + let id = WebGLFramebufferBindingRequest::Explicit(fbo.id()); + self.send_command(WebGLCommand::BindFramebuffer(constants::FRAMEBUFFER, id)); + } + } + + fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> { + // TODO: add a method in WebGLRenderingContext to get the pixels. + None + } + + // Used by HTMLCanvasElement.toDataURL + // + // This emits errors quite liberally, but the spec says that this operation + // can fail and that it is UB what happens in that case. + // + // https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2 + fn get_image_data(&self) -> Option<Vec<u8>> { + handle_potential_webgl_error!(self, self.validate_framebuffer(), return None); + let mut size = self.size().cast(); + + let (fb_width, fb_height) = handle_potential_webgl_error!( + self, + self.get_current_framebuffer_size().ok_or(InvalidOperation), + return None + ); + size.width = cmp::min(size.width, fb_width as u32); + size.height = cmp::min(size.height, fb_height as u32); + + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_command(WebGLCommand::ReadPixels( + Rect::from_size(size), + constants::RGBA, + constants::UNSIGNED_BYTE, + sender, + )); + Some(receiver.recv().unwrap()) + } + + fn mark_as_dirty(&self) { + // If we have a bound framebuffer, then don't mark the canvas as dirty. + if self.bound_draw_framebuffer.get().is_some() { + return; + } + + // Dirtying the canvas is unnecessary if we're actively displaying immersive + // XR content right now. + if self.global().as_window().in_immersive_xr_session() { + return; + } + + match self.canvas { + HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => { + canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + canvas.owner_document().add_dirty_webgl_canvas(self); + }, + HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => {}, + } + } +} + #[cfg(not(feature = "webgl_backtrace"))] #[inline] pub(crate) fn capture_webgl_backtrace<T: DomObject>(_: &T) -> WebGLCommandBacktrace { diff --git a/components/script/dom/webgpu/gpucanvascontext.rs b/components/script/dom/webgpu/gpucanvascontext.rs index e4ef65bea31..3b9abd27dce 100644 --- a/components/script/dom/webgpu/gpucanvascontext.rs +++ b/components/script/dom/webgpu/gpucanvascontext.rs @@ -7,7 +7,6 @@ use std::cell::RefCell; use arrayvec::ArrayVec; use dom_struct::dom_struct; -use euclid::default::Size2D; use ipc_channel::ipc::{self, IpcSharedMemory}; use script_layout_interface::HTMLCanvasDataSource; use webgpu::swapchain::WebGPUContextId; @@ -20,6 +19,7 @@ use webrender_api::ImageKey; use super::gpuconvert::convert_texture_descriptor; use super::gputexture::GPUTexture; +use crate::canvas_context::CanvasContext; use crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods; use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods; @@ -30,7 +30,6 @@ use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ }; use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas; use crate::dom::bindings::error::{Error, Fallible}; -use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomGlobal, Reflector}; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::USVString; @@ -38,20 +37,9 @@ use crate::dom::bindings::weakref::WeakRef; use crate::dom::document::WebGPUContextsMap; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers}; -use crate::dom::node::{Node, NodeDamage, NodeTraits}; +use crate::dom::node::NodeTraits; use crate::script_runtime::CanGc; -impl HTMLCanvasElementOrOffscreenCanvas { - fn size(&self) -> Size2D<u64> { - match self { - HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => { - canvas.get_size().cast() - }, - HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(), - } - } -} - /// <https://gpuweb.github.io/gpuweb/#supported-context-formats> fn supported_context_format(format: GPUTextureFormat) -> bool { // TODO: GPUTextureFormat::Rgba16float @@ -260,43 +248,24 @@ impl GPUCanvasContext { ); } } - - fn size(&self) -> Size2D<u64> { - self.canvas.size() - } } -// public methods for canvas handling -// these methods should probably be behind trait for all canvases -impl GPUCanvasContext { - pub(crate) fn context_id(&self) -> WebGPUContextId { - self.context_id - } +impl CanvasContext for GPUCanvasContext { + type ID = WebGPUContextId; - pub(crate) fn mark_as_dirty(&self) { - if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) = &self.canvas { - canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); - } - } - - pub(crate) fn onscreen(&self) -> bool { - match self.canvas { - HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => { - canvas.upcast::<Node>().is_connected() - }, - // FIXME(#34628): Handle this properly - HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(_) => false, - } + #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Crown is wrong here #35570 + fn context_id(&self) -> WebGPUContextId { + self.context_id } /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas> - pub(crate) fn update_rendering_of_webgpu_canvas(&self) { + fn update_rendering(&self) { // Step 1 self.expire_current_texture(); } /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-update-the-canvas-size> - pub(crate) fn resize(&self) { + fn resize(&self) { // Step 1 self.replace_drawing_buffer(); // Step 2 @@ -309,9 +278,9 @@ impl GPUCanvasContext { } /// <https://gpuweb.github.io/gpuweb/#ref-for-abstract-opdef-get-a-copy-of-the-image-contents-of-a-context%E2%91%A5> - pub(crate) fn get_ipc_image(&self) -> IpcSharedMemory { + fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> { // 1. Return a copy of the image contents of context. - if self.drawing_buffer.borrow().cleared { + Some(if self.drawing_buffer.borrow().cleared { IpcSharedMemory::from_byte(0, self.size().area() as usize * 4) } else { let (sender, receiver) = ipc::channel().unwrap(); @@ -323,11 +292,11 @@ impl GPUCanvasContext { }) .unwrap(); receiver.recv().unwrap() - } + }) } - pub(crate) fn get_image_data(&self) -> Vec<u8> { - self.get_ipc_image().to_vec() + fn canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + self.canvas.clone() } } diff --git a/components/script/dom/webxr/xrlayer.rs b/components/script/dom/webxr/xrlayer.rs index 4db2cfe3ac2..1920e508819 100644 --- a/components/script/dom/webxr/xrlayer.rs +++ b/components/script/dom/webxr/xrlayer.rs @@ -6,6 +6,7 @@ use canvas_traits::webgl::WebGLContextId; use dom_struct::dom_struct; use webxr_api::LayerId; +use crate::canvas_context::CanvasContext as _; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::Dom; use crate::dom::eventtarget::EventTarget; diff --git a/components/script/dom/webxr/xrsession.rs b/components/script/dom/webxr/xrsession.rs index 553789fa6b4..8f888c0efcc 100644 --- a/components/script/dom/webxr/xrsession.rs +++ b/components/script/dom/webxr/xrsession.rs @@ -25,6 +25,7 @@ use webxr_api::{ }; use crate::conversions::Convert; +use crate::canvas_context::CanvasContext; use crate::dom::bindings::trace::HashMapTracedValues; use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::callback::ExceptionHandling; diff --git a/components/script/dom/webxr/xrwebgllayer.rs b/components/script/dom/webxr/xrwebgllayer.rs index df0ff932902..07beeb89afc 100644 --- a/components/script/dom/webxr/xrwebgllayer.rs +++ b/components/script/dom/webxr/xrwebgllayer.rs @@ -10,6 +10,7 @@ use euclid::{Rect, Size2D}; use js::rust::HandleObject; use webxr_api::{ContextId as WebXRContextId, LayerId, LayerInit, Viewport}; +use crate::canvas_context::CanvasContext as _; use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods; use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::{ diff --git a/components/script/lib.rs b/components/script/lib.rs index 1d9b2bc3886..fdbe43d1ba7 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -35,6 +35,7 @@ mod devtools; pub(crate) mod document_loader; #[macro_use] mod dom; +mod canvas_context; mod canvas_state; pub(crate) mod fetch; mod init; |