script/dom/webgl/
webglquery.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use script_bindings::weakref::WeakRef;
9use servo_canvas_traits::webgl::WebGLError::*;
10use servo_canvas_traits::webgl::{WebGLCommand, WebGLQueryId, webgl_channel};
11
12use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
13use crate::dom::bindings::refcounted::Trusted;
14use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
15use crate::dom::bindings::root::DomRoot;
16use crate::dom::webgl::webglobject::WebGLObject;
17use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
18use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
19use crate::script_runtime::CanGc;
20
21#[derive(JSTraceable, MallocSizeOf)]
22struct DroppableWebGLQuery {
23    context: WeakRef<WebGLRenderingContext>,
24    #[no_trace]
25    gl_id: WebGLQueryId,
26    marked_for_deletion: Cell<bool>,
27}
28
29impl DroppableWebGLQuery {
30    fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
31        if let Some(root) = self.context.root() {
32            let result = root.sender().send(command, capture_webgl_backtrace());
33            if matches!(fallibility, Operation::Infallible) {
34                result.expect("Operation failed");
35            }
36        }
37    }
38
39    fn delete(&self, operation_fallibility: Operation) {
40        if !self.marked_for_deletion.get() {
41            self.marked_for_deletion.set(true);
42            self.send_with_fallibility(
43                WebGLCommand::DeleteQuery(self.gl_id),
44                operation_fallibility,
45            );
46        }
47    }
48}
49
50impl Drop for DroppableWebGLQuery {
51    fn drop(&mut self) {
52        self.delete(Operation::Fallible);
53    }
54}
55
56#[dom_struct(associated_memory)]
57pub(crate) struct WebGLQuery {
58    webgl_object: WebGLObject,
59    gl_target: Cell<Option<u32>>,
60    query_result_available: Cell<Option<u32>>,
61    query_result: Cell<u32>,
62    droppable: DroppableWebGLQuery,
63}
64
65impl WebGLQuery {
66    fn new_inherited(context: &WebGLRenderingContext, id: WebGLQueryId) -> Self {
67        Self {
68            webgl_object: WebGLObject::new_inherited(context),
69            gl_target: Cell::new(None),
70            query_result_available: Cell::new(None),
71            query_result: Cell::new(0),
72            droppable: DroppableWebGLQuery {
73                context: WeakRef::new(context),
74                gl_id: id,
75                marked_for_deletion: Cell::new(false),
76            },
77        }
78    }
79
80    pub(crate) fn new(context: &WebGLRenderingContext, can_gc: CanGc) -> DomRoot<Self> {
81        let (sender, receiver) = webgl_channel().unwrap();
82        context.send_command(WebGLCommand::GenerateQuery(sender));
83        let id = receiver.recv().unwrap();
84
85        reflect_dom_object(
86            Box::new(Self::new_inherited(context, id)),
87            &*context.global(),
88            can_gc,
89        )
90    }
91
92    pub(crate) fn begin(
93        &self,
94        context: &WebGLRenderingContext,
95        target: u32,
96    ) -> Result<(), servo_canvas_traits::webgl::WebGLError> {
97        if self.droppable.marked_for_deletion.get() {
98            return Err(InvalidOperation);
99        }
100        if let Some(current_target) = self.gl_target.get() {
101            if current_target != target {
102                return Err(InvalidOperation);
103            }
104        }
105        match target {
106            constants::ANY_SAMPLES_PASSED |
107            constants::ANY_SAMPLES_PASSED_CONSERVATIVE |
108            constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => (),
109            _ => return Err(InvalidEnum),
110        }
111        self.gl_target.set(Some(target));
112
113        context.send_command(WebGLCommand::BeginQuery(target, self.droppable.gl_id));
114        Ok(())
115    }
116
117    pub(crate) fn end(
118        &self,
119        context: &WebGLRenderingContext,
120        target: u32,
121    ) -> Result<(), servo_canvas_traits::webgl::WebGLError> {
122        if self.droppable.marked_for_deletion.get() {
123            return Err(InvalidOperation);
124        }
125        if let Some(current_target) = self.gl_target.get() {
126            if current_target != target {
127                return Err(InvalidOperation);
128            }
129        }
130        match target {
131            constants::ANY_SAMPLES_PASSED |
132            constants::ANY_SAMPLES_PASSED_CONSERVATIVE |
133            constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => (),
134            _ => return Err(InvalidEnum),
135        }
136        context.send_command(WebGLCommand::EndQuery(target));
137        Ok(())
138    }
139
140    pub(crate) fn delete(&self, operation_fallibility: Operation) {
141        self.droppable.delete(operation_fallibility);
142    }
143
144    pub(crate) fn is_valid(&self) -> bool {
145        !self.droppable.marked_for_deletion.get() && self.target().is_some()
146    }
147
148    pub(crate) fn target(&self) -> Option<u32> {
149        self.gl_target.get()
150    }
151
152    fn update_results(&self, context: &WebGLRenderingContext) {
153        let (sender, receiver) = webgl_channel().unwrap();
154        context.send_command(WebGLCommand::GetQueryState(
155            sender,
156            self.droppable.gl_id,
157            constants::QUERY_RESULT_AVAILABLE,
158        ));
159        let is_available = receiver.recv().unwrap();
160        if is_available == 0 {
161            self.query_result_available.set(None);
162            return;
163        }
164
165        let (sender, receiver) = webgl_channel().unwrap();
166        context.send_command(WebGLCommand::GetQueryState(
167            sender,
168            self.droppable.gl_id,
169            constants::QUERY_RESULT,
170        ));
171
172        self.query_result.set(receiver.recv().unwrap());
173        self.query_result_available.set(Some(is_available));
174    }
175
176    #[rustfmt::skip]
177    pub(crate) fn get_parameter(
178        &self,
179        context: &WebGLRenderingContext,
180        pname: u32,
181    ) -> Result<u32, servo_canvas_traits::webgl::WebGLError> {
182        if !self.is_valid() {
183            return Err(InvalidOperation);
184        }
185        match pname {
186            constants::QUERY_RESULT |
187            constants::QUERY_RESULT_AVAILABLE => {},
188            _ => return Err(InvalidEnum),
189        }
190
191        if self.query_result_available.get().is_none() {
192            self.query_result_available.set(Some(0));
193
194            let this = Trusted::new(self);
195            let context = Trusted::new(context);
196            let task = task!(request_query_state: move || {
197                let this = this.root();
198                let context = context.root();
199                this.update_results(&context);
200            });
201
202            self.global()
203                .task_manager()
204                .dom_manipulation_task_source()
205                .queue(task);
206        }
207
208        match pname {
209            constants::QUERY_RESULT => Ok(self.query_result.get()),
210            constants::QUERY_RESULT_AVAILABLE => Ok(self.query_result_available.get().unwrap()),
211            _ => unreachable!(),
212        }
213    }
214}