Files
servo/components/script/dom/webgl/webglrenderbuffer.rs
Martin Robinson 1a99f74c36 script: Use a WeakRef for references to WebGLRenderingContext (#40725)
Many WebGL objects refer to a `WebGLRenderingContext` and rely on it for
messaging to the `WebGLThread`. This poses a problem, because WebGL
objects often need to send a message to the `WebGLThread` during their
`Drop` implementation. If the `Drop` is triggered as part of garbage
collection, references to the `WebGLRenderingContext` might be invalid,
if they were garbage collected first as part of the same harvest.

This change makes it so that all of these objects store a `WeakRef`
instead of a `Dom<>`. The `WeakRef` is only used if it can be rooted,
otherwise a `ContextLost` error is given. In cases where only messaging
is needed, a cloned `WebGLMsgSender` is used to perform messages
regardless of whether the context is garbage collected or not.

This isn't a replacement for #37622, but should make it easier to
implement as
the `WebGLMsgSender` and the `WeakRef` could be stored in the droppable
portion of the DOM object.

Testing: This fixes a use-after-free issue which is mainly detectable
via ASAN
builds. Since we do not run ASAN on CI, this is a bit hard to create
automated
tests for. I verified that this fixed the issue manually.
Fixes: #40655.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2025-11-19 12:29:46 +00:00

286 lines
9.7 KiB
Rust

/* 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/. */
// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use std::cell::Cell;
use canvas_traits::webgl::{
GlType, InternalFormatIntVec, WebGLCommand, WebGLError, WebGLRenderbufferId, WebGLResult,
WebGLVersion, webgl_channel,
};
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::EXTColorBufferHalfFloatBinding::EXTColorBufferHalfFloatConstants;
use crate::dom::bindings::codegen::Bindings::WEBGLColorBufferFloatBinding::WEBGLColorBufferFloatConstants;
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::webgl::webglframebuffer::WebGLFramebuffer;
use crate::dom::webgl::webglobject::WebGLObject;
use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct WebGLRenderbuffer {
webgl_object: WebGLObject,
#[no_trace]
id: WebGLRenderbufferId,
ever_bound: Cell<bool>,
is_deleted: Cell<bool>,
size: Cell<Option<(i32, i32)>>,
internal_format: Cell<Option<u32>>,
is_initialized: Cell<bool>,
attached_framebuffer: MutNullableDom<WebGLFramebuffer>,
}
impl WebGLRenderbuffer {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> Self {
Self {
webgl_object: WebGLObject::new_inherited(context),
id,
ever_bound: Cell::new(false),
is_deleted: Cell::new(false),
internal_format: Cell::new(None),
size: Cell::new(None),
is_initialized: Cell::new(false),
attached_framebuffer: Default::default(),
}
}
pub(crate) fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> {
let (sender, receiver) = webgl_channel().unwrap();
context.send_command(WebGLCommand::CreateRenderbuffer(sender));
receiver
.recv()
.unwrap()
.map(|id| WebGLRenderbuffer::new(context, id, CanGc::note()))
}
pub(crate) fn new(
context: &WebGLRenderingContext,
id: WebGLRenderbufferId,
can_gc: CanGc,
) -> DomRoot<Self> {
reflect_dom_object(
Box::new(WebGLRenderbuffer::new_inherited(context, id)),
&*context.global(),
can_gc,
)
}
}
impl WebGLRenderbuffer {
pub(crate) fn id(&self) -> WebGLRenderbufferId {
self.id
}
pub(crate) fn size(&self) -> Option<(i32, i32)> {
self.size.get()
}
pub(crate) fn internal_format(&self) -> u32 {
self.internal_format.get().unwrap_or(constants::RGBA4)
}
pub(crate) fn mark_initialized(&self) {
self.is_initialized.set(true);
}
pub(crate) fn is_initialized(&self) -> bool {
self.is_initialized.get()
}
pub(crate) fn bind(&self, target: u32) {
self.ever_bound.set(true);
self.upcast()
.send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id)));
}
pub(crate) fn delete(&self, operation_fallibility: Operation) {
if !self.is_deleted.get() {
self.is_deleted.set(true);
/*
If a renderbuffer object is deleted while its image is attached to one or more
attachment points in a currently bound framebuffer object, then it is as if
FramebufferRenderbuffer had been called, with a renderbuffer of zero, for each
attachment point to which this image was attached in that framebuffer object.
In other words,the renderbuffer image is first detached from all attachment points
in that frame-buffer object.
- GLES 3.0, 4.4.2.3, "Attaching Renderbuffer Images to a Framebuffer"
*/
let webgl_object = self.upcast();
if let Some(context) = webgl_object.context() {
if let Some(fb) = context.get_draw_framebuffer_slot().get() {
let _ = fb.detach_renderbuffer(self);
}
if let Some(fb) = context.get_read_framebuffer_slot().get() {
let _ = fb.detach_renderbuffer(self);
}
}
webgl_object.send_with_fallibility(
WebGLCommand::DeleteRenderbuffer(self.id),
operation_fallibility,
);
}
}
pub(crate) fn is_deleted(&self) -> bool {
self.is_deleted.get()
}
pub(crate) fn ever_bound(&self) -> bool {
self.ever_bound.get()
}
pub(crate) fn storage(
&self,
api_type: GlType,
sample_count: i32,
internal_format: u32,
width: i32,
height: i32,
) -> WebGLResult<()> {
let Some(context) = self.upcast().context() else {
return Err(WebGLError::ContextLost);
};
let webgl_version = context.webgl_version();
let is_gles = api_type == GlType::Gles;
// Validate the internal_format, and save it for completeness
// validation.
let actual_format = match internal_format {
constants::RGBA4 | constants::DEPTH_COMPONENT16 | constants::STENCIL_INDEX8 => {
internal_format
},
constants::R8 |
constants::R8UI |
constants::R8I |
constants::R16UI |
constants::R16I |
constants::R32UI |
constants::R32I |
constants::RG8 |
constants::RG8UI |
constants::RG8I |
constants::RG16UI |
constants::RG16I |
constants::RG32UI |
constants::RG32I |
constants::RGB8 |
constants::RGBA8 |
constants::SRGB8_ALPHA8 |
constants::RGB10_A2 |
constants::RGBA8UI |
constants::RGBA8I |
constants::RGB10_A2UI |
constants::RGBA16UI |
constants::RGBA16I |
constants::RGBA32I |
constants::RGBA32UI |
constants::DEPTH_COMPONENT24 |
constants::DEPTH_COMPONENT32F |
constants::DEPTH24_STENCIL8 |
constants::DEPTH32F_STENCIL8 => match webgl_version {
WebGLVersion::WebGL1 => return Err(WebGLError::InvalidEnum),
_ => internal_format,
},
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.8
constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8,
constants::RGB5_A1 => {
// 16-bit RGBA formats are not supported on desktop GL.
if is_gles {
constants::RGB5_A1
} else {
constants::RGBA8
}
},
constants::RGB565 => {
// RGB565 is not supported on desktop GL.
if is_gles {
constants::RGB565
} else {
constants::RGB8
}
},
EXTColorBufferHalfFloatConstants::RGBA16F_EXT |
EXTColorBufferHalfFloatConstants::RGB16F_EXT => {
if !context
.extension_manager()
.is_half_float_buffer_renderable()
{
return Err(WebGLError::InvalidEnum);
}
internal_format
},
WEBGLColorBufferFloatConstants::RGBA32F_EXT => {
if !context.extension_manager().is_float_buffer_renderable() {
return Err(WebGLError::InvalidEnum);
}
internal_format
},
_ => return Err(WebGLError::InvalidEnum),
};
if webgl_version != WebGLVersion::WebGL1 {
let (sender, receiver) = webgl_channel().unwrap();
self.upcast()
.send_command(WebGLCommand::GetInternalFormatIntVec(
constants::RENDERBUFFER,
internal_format,
InternalFormatIntVec::Samples,
sender,
));
let samples = receiver.recv().unwrap();
if sample_count < 0 || sample_count > samples.first().cloned().unwrap_or(0) {
return Err(WebGLError::InvalidOperation);
}
}
self.internal_format.set(Some(internal_format));
self.is_initialized.set(false);
if let Some(fb) = self.attached_framebuffer.get() {
fb.update_status();
}
let command = match sample_count {
0 => WebGLCommand::RenderbufferStorage(
constants::RENDERBUFFER,
actual_format,
width,
height,
),
_ => WebGLCommand::RenderbufferStorageMultisample(
constants::RENDERBUFFER,
sample_count,
actual_format,
width,
height,
),
};
self.upcast().send_command(command);
self.size.set(Some((width, height)));
Ok(())
}
pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
self.attached_framebuffer.set(Some(fb));
}
pub(crate) fn detach_from_framebuffer(&self) {
self.attached_framebuffer.set(None);
}
}
impl Drop for WebGLRenderbuffer {
fn drop(&mut self) {
self.delete(Operation::Fallible);
}
}