Files
servo/components/script/dom/webgl/webglquery.rs
Sam 0f9f601eb9 script: Correctly handle Reflector with AssociatedMemory and remove associated memory in finalize instead of drop (#42271)
Reviewable per commits:

As noted in
https://github.com/servo/servo/pull/42180#discussion_r2749861902
transmuting `&Reflector<AssociatedMemory>` to `&Reflector<()>` will
ignore AssociatedMemory information and thus it will not remove extra
associated memory. By returning `&Reflector<Self::ReflectorType>` from
`DomObject::reflector()` we will preserve this information and thus
correctly handle cases with associated memory. We also do not need
`overrideMemoryUsage` in bindings.conf anymore. 🎉

Instead of removing associated memory in drop code we should do it as
part of finalizers, otherwise we have problems in nested (inherited)
structs, where drop is run for both parent and child, but we only added
associated memory for child (on init_reflector) which already included
size of parent. The only exception here is promise, because it is RCed
and not finalized.

Testing: Tested locally that it fixes speedometer.
Fixes #42269

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
2026-02-01 10:57:08 +00:00

190 lines
6.0 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/. */
use std::cell::Cell;
use canvas_traits::webgl::WebGLError::*;
use canvas_traits::webgl::{WebGLCommand, WebGLQueryId, webgl_channel};
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
use crate::dom::webgl::webglobject::WebGLObject;
use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct WebGLQuery {
webgl_object: WebGLObject,
#[no_trace]
gl_id: WebGLQueryId,
gl_target: Cell<Option<u32>>,
marked_for_deletion: Cell<bool>,
query_result_available: Cell<Option<u32>>,
query_result: Cell<u32>,
}
impl WebGLQuery {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLQueryId) -> Self {
Self {
webgl_object: WebGLObject::new_inherited(context),
gl_id: id,
gl_target: Cell::new(None),
marked_for_deletion: Cell::new(false),
query_result_available: Cell::new(None),
query_result: Cell::new(0),
}
}
pub(crate) fn new(context: &WebGLRenderingContext, can_gc: CanGc) -> DomRoot<Self> {
let (sender, receiver) = webgl_channel().unwrap();
context.send_command(WebGLCommand::GenerateQuery(sender));
let id = receiver.recv().unwrap();
reflect_dom_object(
Box::new(Self::new_inherited(context, id)),
&*context.global(),
can_gc,
)
}
pub(crate) fn begin(
&self,
context: &WebGLRenderingContext,
target: u32,
) -> Result<(), canvas_traits::webgl::WebGLError> {
if self.marked_for_deletion.get() {
return Err(InvalidOperation);
}
if let Some(current_target) = self.gl_target.get() {
if current_target != target {
return Err(InvalidOperation);
}
}
match target {
constants::ANY_SAMPLES_PASSED |
constants::ANY_SAMPLES_PASSED_CONSERVATIVE |
constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => (),
_ => return Err(InvalidEnum),
}
self.gl_target.set(Some(target));
context.send_command(WebGLCommand::BeginQuery(target, self.gl_id));
Ok(())
}
pub(crate) fn end(
&self,
context: &WebGLRenderingContext,
target: u32,
) -> Result<(), canvas_traits::webgl::WebGLError> {
if self.marked_for_deletion.get() {
return Err(InvalidOperation);
}
if let Some(current_target) = self.gl_target.get() {
if current_target != target {
return Err(InvalidOperation);
}
}
match target {
constants::ANY_SAMPLES_PASSED |
constants::ANY_SAMPLES_PASSED_CONSERVATIVE |
constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => (),
_ => return Err(InvalidEnum),
}
context.send_command(WebGLCommand::EndQuery(target));
Ok(())
}
pub(crate) fn delete(&self, operation_fallibility: Operation) {
if !self.marked_for_deletion.get() {
self.marked_for_deletion.set(true);
self.upcast().send_with_fallibility(
WebGLCommand::DeleteQuery(self.gl_id),
operation_fallibility,
);
}
}
pub(crate) fn is_valid(&self) -> bool {
!self.marked_for_deletion.get() && self.target().is_some()
}
pub(crate) fn target(&self) -> Option<u32> {
self.gl_target.get()
}
fn update_results(&self, context: &WebGLRenderingContext) {
let (sender, receiver) = webgl_channel().unwrap();
context.send_command(WebGLCommand::GetQueryState(
sender,
self.gl_id,
constants::QUERY_RESULT_AVAILABLE,
));
let is_available = receiver.recv().unwrap();
if is_available == 0 {
self.query_result_available.set(None);
return;
}
let (sender, receiver) = webgl_channel().unwrap();
context.send_command(WebGLCommand::GetQueryState(
sender,
self.gl_id,
constants::QUERY_RESULT,
));
self.query_result.set(receiver.recv().unwrap());
self.query_result_available.set(Some(is_available));
}
#[rustfmt::skip]
pub(crate) fn get_parameter(
&self,
context: &WebGLRenderingContext,
pname: u32,
) -> Result<u32, canvas_traits::webgl::WebGLError> {
if !self.is_valid() {
return Err(InvalidOperation);
}
match pname {
constants::QUERY_RESULT |
constants::QUERY_RESULT_AVAILABLE => {},
_ => return Err(InvalidEnum),
}
if self.query_result_available.get().is_none() {
self.query_result_available.set(Some(0));
let this = Trusted::new(self);
let context = Trusted::new(context);
let task = task!(request_query_state: move || {
let this = this.root();
let context = context.root();
this.update_results(&context);
});
self.global()
.task_manager()
.dom_manipulation_task_source()
.queue(task);
}
match pname {
constants::QUERY_RESULT => Ok(self.query_result.get()),
constants::QUERY_RESULT_AVAILABLE => Ok(self.query_result_available.get().unwrap()),
_ => unreachable!(),
}
}
}
impl Drop for WebGLQuery {
fn drop(&mut self) {
self.delete(Operation::Fallible);
}
}