Skip to main content

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