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>
This commit is contained in:
Samson 2025-02-21 21:26:27 +01:00 committed by GitHub
parent 084006abb6
commit a6f19c0092
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 317 additions and 210 deletions

View file

@ -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(),
}
}
}

View file

@ -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/
//

View file

@ -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>>>> {

View file

@ -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(),
_ => (),
};
}

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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 {
impl CanvasContext for GPUCanvasContext {
type ID = WebGPUContextId;
#[cfg_attr(crown, allow(crown::unrooted_must_root))] // Crown is wrong here #35570
fn context_id(&self) -> WebGPUContextId {
self.context_id
}
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,
}
}
/// <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()
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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::{

View file

@ -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;