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

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