Skip to main content

script/dom/webgl/
webglrenderingcontext.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::borrow::Cow;
6use std::cell::Cell;
7use std::rc::Rc;
8use std::{cmp, ptr};
9
10#[cfg(feature = "webgl_backtrace")]
11use backtrace::Backtrace;
12use bitflags::bitflags;
13use dom_struct::dom_struct;
14use euclid::default::{Point2D, Rect, Size2D};
15use js::context::{JSContext, NoGC};
16use js::conversions::ToJSValConvertible;
17use js::jsapi::{JSObject, Type};
18use js::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, UInt32Value};
19use js::rust::{CustomAutoRooterGuard, MutableHandleObject, MutableHandleValue};
20use js::typedarray::{ArrayBufferView, Float32, Int32, TypedArrayElementCreator, Uint32};
21use pixels::{self, Alpha, PixelFormat, Snapshot, SnapshotPixelFormat};
22use script_bindings::cell::{DomRefCell, Ref, RefMut};
23use script_bindings::reflector::{
24    AssociatedMemory, Reflector, reflect_weak_referenceable_dom_object,
25};
26use serde::{Deserialize, Serialize};
27use servo_base::generic_channel::GenericSharedMemory;
28use servo_base::{Epoch, generic_channel};
29use servo_canvas_traits::webgl::WebGLError::*;
30use servo_canvas_traits::webgl::{
31    AlphaTreatment, GLContextAttributes, GLLimits, GlType, Parameter, ParameterString,
32    SizedDataType, TexComponentType, TexDataType, TexFormat, TexParameter, WebGLCommand,
33    WebGLCommandBacktrace, WebGLContextId, WebGLError, WebGLFramebufferBindingRequest, WebGLMsg,
34    WebGLMsgSender, WebGLProgramId, WebGLResult, WebGLSLVersion, WebGLVersion, YAxisTreatment,
35    webgl_channel,
36};
37use servo_config::pref;
38use webrender_api::ImageKey;
39
40use crate::canvas_context::{CanvasContext, HTMLCanvasElementOrOffscreenCanvas};
41#[cfg(feature = "webxr")]
42use crate::dom::RootedPromise;
43use crate::dom::bindings::buffer_source::{create_buffer_source, get_buffer_source_slice};
44use crate::dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants;
45use crate::dom::bindings::codegen::Bindings::EXTBlendMinmaxBinding::EXTBlendMinmaxConstants;
46use crate::dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants;
47use crate::dom::bindings::codegen::Bindings::WEBGLDebugRendererInfoBinding::WEBGLDebugRendererInfoConstants;
48use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants;
49use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{
50    TexImageSource, WebGLContextAttributes, WebGLRenderingContextConstants as constants,
51    WebGLRenderingContextMethods,
52};
53use crate::dom::bindings::codegen::UnionTypes::{
54    ArrayBufferViewOrArrayBuffer, Float32ArrayOrUnrestrictedFloatSequence,
55    HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas,
56    Int32ArrayOrLongSequence,
57};
58use crate::dom::bindings::conversions::DerivedFrom;
59use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
60use crate::dom::bindings::inheritance::Castable;
61use crate::dom::bindings::reflector::DomGlobal;
62use crate::dom::bindings::root::{DomOnceCell, DomRoot, MutNullableDom};
63use crate::dom::bindings::str::DOMString;
64use crate::dom::event::{Event, EventBubbles, EventCancelable};
65#[cfg(feature = "webgl_backtrace")]
66use crate::dom::globalscope::GlobalScope;
67use crate::dom::node::NodeTraits;
68#[cfg(feature = "webxr")]
69use crate::dom::promise::Promise;
70use crate::dom::webgl::extensions::WebGLExtensions;
71use crate::dom::webgl::validations::WebGLValidator;
72use crate::dom::webgl::validations::tex_image_2d::{
73    CommonCompressedTexImage2DValidatorResult, CommonTexImage2DValidator,
74    CommonTexImage2DValidatorResult, CompressedTexImage2DValidator,
75    CompressedTexSubImage2DValidator, TexImage2DValidator, TexImage2DValidatorResult,
76};
77use crate::dom::webgl::validations::types::TexImageTarget;
78use crate::dom::webgl::vertexarrayobject::{VertexAttribData, VertexAttribPointerKind};
79use crate::dom::webgl::webglactiveinfo::WebGLActiveInfo;
80use crate::dom::webgl::webglbuffer::WebGLBuffer;
81use crate::dom::webgl::webglcontextevent::WebGLContextEvent;
82use crate::dom::webgl::webglframebuffer::{
83    CompleteForRendering, WebGLFramebuffer, WebGLFramebufferAttachmentRoot,
84};
85use crate::dom::webgl::webglobject::WebGLObject;
86use crate::dom::webgl::webglprogram::WebGLProgram;
87use crate::dom::webgl::webglrenderbuffer::WebGLRenderbuffer;
88use crate::dom::webgl::webglshader::WebGLShader;
89use crate::dom::webgl::webglshaderprecisionformat::WebGLShaderPrecisionFormat;
90use crate::dom::webgl::webgltexture::{TexParameterValue, WebGLTexture};
91use crate::dom::webgl::webgluniformlocation::WebGLUniformLocation;
92use crate::dom::webgl::webglvertexarrayobject::WebGLVertexArrayObject;
93use crate::dom::webgl::webglvertexarrayobjectoes::WebGLVertexArrayObjectOES;
94use crate::dom::window::Window;
95
96fn has_invalid_blend_constants(arg1: u32, arg2: u32) -> bool {
97    match (arg1, arg2) {
98        (constants::CONSTANT_COLOR, constants::CONSTANT_ALPHA) => true,
99        (constants::ONE_MINUS_CONSTANT_COLOR, constants::ONE_MINUS_CONSTANT_ALPHA) => true,
100        (constants::ONE_MINUS_CONSTANT_COLOR, constants::CONSTANT_ALPHA) => true,
101        (constants::CONSTANT_COLOR, constants::ONE_MINUS_CONSTANT_ALPHA) => true,
102        (_, _) => false,
103    }
104}
105
106pub(crate) fn uniform_get<T, F>(triple: (&WebGLRenderingContext, WebGLProgramId, i32), f: F) -> T
107where
108    F: FnOnce(WebGLProgramId, i32, generic_channel::GenericSender<T>) -> WebGLCommand,
109    T: for<'de> Deserialize<'de> + Serialize,
110{
111    let (sender, receiver) = webgl_channel().unwrap();
112    triple.0.send_command(f(triple.1, triple.2, sender));
113    receiver.recv().unwrap()
114}
115
116pub(crate) fn uniform_typed<T>(
117    cx: &mut JSContext,
118    value: &[T::Element],
119    mut retval: MutableHandleValue,
120) where
121    T: TypedArrayElementCreator + 'static,
122{
123    rooted!(&in(cx) let mut rval = ptr::null_mut::<JSObject>());
124    create_buffer_source::<T>(cx, value, rval.handle_mut()).unwrap();
125    retval.set(ObjectValue(rval.get()));
126}
127
128/// Set of bitflags for texture unpacking (texImage2d, etc...)
129#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
130pub(crate) struct TextureUnpacking(u8);
131
132bitflags! {
133    impl TextureUnpacking: u8 {
134        const FLIP_Y_AXIS = 0x01;
135        const PREMULTIPLY_ALPHA = 0x02;
136        const CONVERT_COLORSPACE = 0x04;
137    }
138}
139
140/// WebGL 2 `UNPACK_*` pixel store state that selects a block of client memory.
141#[derive(Clone, Copy, Default, JSTraceable, MallocSizeOf, PartialEq)]
142pub(crate) struct UnpackLayout {
143    pub(crate) row_length: u32,
144    pub(crate) image_height: u32,
145    pub(crate) skip_pixels: u32,
146    pub(crate) skip_rows: u32,
147    pub(crate) skip_images: u32,
148}
149
150#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
151pub(crate) enum VertexAttrib {
152    Float(f32, f32, f32, f32),
153    Int(i32, i32, i32, i32),
154    Uint(u32, u32, u32, u32),
155}
156
157#[derive(Clone, Copy, Debug)]
158pub(crate) enum Operation {
159    Fallible,
160    Infallible,
161}
162
163#[derive(JSTraceable, MallocSizeOf)]
164struct DroppableWebGLRenderingContext {
165    #[no_trace]
166    webgl_sender: WebGLMsgSender,
167}
168
169impl Drop for DroppableWebGLRenderingContext {
170    fn drop(&mut self) {
171        let _ = self.webgl_sender.send_remove();
172    }
173}
174
175#[dom_struct]
176pub(crate) struct WebGLRenderingContext {
177    reflector_: Reflector<AssociatedMemory>,
178    #[no_trace]
179    webgl_version: WebGLVersion,
180    #[no_trace]
181    glsl_version: WebGLSLVersion,
182    #[ignore_malloc_size_of = "Defined in surfman"]
183    #[no_trace]
184    limits: GLLimits,
185    canvas: HTMLCanvasElementOrOffscreenCanvas,
186    #[ignore_malloc_size_of = "Defined in servo_canvas_traits"]
187    #[no_trace]
188    last_error: Cell<Option<WebGLError>>,
189    texture_packing_alignment: Cell<u8>,
190    texture_unpacking_settings: Cell<TextureUnpacking>,
191    // TODO(nox): Should be Cell<u8>.
192    texture_unpacking_alignment: Cell<u32>,
193    texture_unpack_layout: Cell<UnpackLayout>,
194    bound_draw_framebuffer: MutNullableDom<WebGLFramebuffer>,
195    // TODO(mmatyas): This was introduced in WebGL2, but listed here because it's used by
196    // Textures and Renderbuffers, but such WebGLObjects have access only to the GL1 context.
197    bound_read_framebuffer: MutNullableDom<WebGLFramebuffer>,
198    bound_renderbuffer: MutNullableDom<WebGLRenderbuffer>,
199    bound_buffer_array: MutNullableDom<WebGLBuffer>,
200    current_program: MutNullableDom<WebGLProgram>,
201    current_vertex_attribs: DomRefCell<Box<[VertexAttrib]>>,
202    current_scissor: Cell<(i32, i32, u32, u32)>,
203    current_clear_color: Cell<(f32, f32, f32, f32)>,
204    #[no_trace]
205    size: Cell<Size2D<u32>>,
206    extension_manager: WebGLExtensions,
207    capabilities: Capabilities,
208    default_vao: DomOnceCell<WebGLVertexArrayObjectOES>,
209    current_vao: MutNullableDom<WebGLVertexArrayObjectOES>,
210    default_vao_webgl2: DomOnceCell<WebGLVertexArrayObject>,
211    current_vao_webgl2: MutNullableDom<WebGLVertexArrayObject>,
212    textures: Textures,
213    #[no_trace]
214    api_type: GlType,
215    droppable: DroppableWebGLRenderingContext,
216}
217
218impl WebGLRenderingContext {
219    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
220    pub(crate) fn new_inherited(
221        window: &Window,
222        canvas: HTMLCanvasElementOrOffscreenCanvas,
223        webgl_version: WebGLVersion,
224        size: Size2D<u32>,
225        attrs: GLContextAttributes,
226    ) -> Result<WebGLRenderingContext, String> {
227        if pref!(webgl_testing_context_creation_error) {
228            return Err("WebGL context creation error forced by pref `webgl.testing.context_creation_error`".into());
229        }
230
231        let webgl_chan = match window.webgl_chan() {
232            Some(chan) => chan,
233            None => return Err("WebGL initialization failed early on".into()),
234        };
235
236        let (sender, receiver) = webgl_channel().unwrap();
237        webgl_chan
238            .send(WebGLMsg::CreateContext(
239                window.webview_id().into(),
240                webgl_version,
241                size,
242                attrs,
243                sender,
244            ))
245            .unwrap();
246        let result = receiver.recv().unwrap();
247
248        result.map(|ctx_data| {
249            let max_combined_texture_image_units = ctx_data.limits.max_combined_texture_image_units;
250            let max_vertex_attribs = ctx_data.limits.max_vertex_attribs as usize;
251            Self {
252                reflector_: Reflector::new(),
253                webgl_version,
254                glsl_version: ctx_data.glsl_version,
255                limits: ctx_data.limits,
256                canvas,
257                last_error: Cell::new(None),
258                texture_packing_alignment: Cell::new(4),
259                texture_unpacking_settings: Cell::new(TextureUnpacking::CONVERT_COLORSPACE),
260                texture_unpacking_alignment: Cell::new(4),
261                texture_unpack_layout: Cell::default(),
262                bound_draw_framebuffer: MutNullableDom::new(None),
263                bound_read_framebuffer: MutNullableDom::new(None),
264                bound_buffer_array: MutNullableDom::new(None),
265                bound_renderbuffer: MutNullableDom::new(None),
266                current_program: MutNullableDom::new(None),
267                current_vertex_attribs: DomRefCell::new(
268                    vec![VertexAttrib::Float(0f32, 0f32, 0f32, 1f32); max_vertex_attribs].into(),
269                ),
270                current_scissor: Cell::new((0, 0, size.width, size.height)),
271                // FIXME(#21718) The backend is allowed to choose a size smaller than
272                // what was requested
273                size: Cell::new(size),
274                current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)),
275                extension_manager: WebGLExtensions::new(
276                    webgl_version,
277                    ctx_data.api_type,
278                    ctx_data.glsl_version,
279                ),
280                capabilities: Default::default(),
281                default_vao: Default::default(),
282                current_vao: Default::default(),
283                default_vao_webgl2: Default::default(),
284                current_vao_webgl2: Default::default(),
285                textures: Textures::new(max_combined_texture_image_units),
286                api_type: ctx_data.api_type,
287                droppable: DroppableWebGLRenderingContext {
288                    webgl_sender: ctx_data.sender,
289                },
290            }
291        })
292    }
293
294    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
295    pub(crate) fn new(
296        cx: &mut JSContext,
297        window: &Window,
298        canvas: &RootedHTMLCanvasElementOrOffscreenCanvas,
299        webgl_version: WebGLVersion,
300        size: Size2D<u32>,
301        attrs: GLContextAttributes,
302    ) -> Option<DomRoot<WebGLRenderingContext>> {
303        match WebGLRenderingContext::new_inherited(
304            window,
305            HTMLCanvasElementOrOffscreenCanvas::from(canvas),
306            webgl_version,
307            size,
308            attrs,
309        ) {
310            Ok(ctx) => Some(reflect_weak_referenceable_dom_object(
311                cx,
312                Rc::new(ctx),
313                window,
314            )),
315            Err(msg) => {
316                error!("Couldn't create WebGLRenderingContext: {}", msg);
317                let event = WebGLContextEvent::new(
318                    cx,
319                    window,
320                    atom!("webglcontextcreationerror"),
321                    EventBubbles::DoesNotBubble,
322                    EventCancelable::Cancelable,
323                    DOMString::from(msg),
324                );
325                match canvas {
326                    RootedHTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
327                        event.upcast::<Event>().fire(cx, canvas.upcast());
328                    },
329                    RootedHTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => {
330                        event.upcast::<Event>().fire(cx, canvas.upcast());
331                    },
332                }
333                None
334            },
335        }
336    }
337
338    pub(crate) fn set_image_key(&self, image_key: ImageKey) {
339        self.droppable.webgl_sender.set_image_key(image_key);
340    }
341
342    pub(crate) fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
343        if !self.onscreen() {
344            return false;
345        }
346
347        let global = self.global();
348        let Some(window) = global.downcast::<Window>() else {
349            return false;
350        };
351
352        window
353            .webgl_chan()
354            .expect("Where's the WebGL channel?")
355            .send(WebGLMsg::SwapBuffers(
356                vec![self.context_id()],
357                Some(canvas_epoch),
358                0, /* time */
359            ))
360            .is_ok()
361    }
362
363    pub(crate) fn webgl_version(&self) -> WebGLVersion {
364        self.webgl_version
365    }
366
367    pub(crate) fn api_type(&self) -> GlType {
368        self.api_type
369    }
370
371    pub(crate) fn limits(&self) -> &GLLimits {
372        &self.limits
373    }
374
375    pub(crate) fn texture_unpacking_alignment(&self) -> u32 {
376        self.texture_unpacking_alignment.get()
377    }
378
379    pub(crate) fn texture_unpack_layout(&self) -> UnpackLayout {
380        self.texture_unpack_layout.get()
381    }
382
383    pub(crate) fn set_texture_unpack_layout(&self, layout: UnpackLayout) {
384        self.texture_unpack_layout.set(layout);
385    }
386
387    /// Copies a `width` x `height` x `depth` block of `cpp`-byte pixels out of `data` under the
388    /// unpack layout, as rows padded to the unpack alignment; `None` depth is a 2D upload.
389    /// Generates `INVALID_OPERATION` when the block does not fit.
390    pub(crate) fn unpack_block<'a>(
391        &self,
392        data: &'a [u8],
393        width: u32,
394        height: u32,
395        depth: Option<u32>,
396        cpp: u32,
397    ) -> Result<Cow<'a, [u8]>, ()> {
398        let mut layout = self.texture_unpack_layout.get();
399        if depth.is_none() {
400            layout.image_height = 0;
401            layout.skip_images = 0;
402        }
403        if layout == UnpackLayout::default() {
404            return Ok(Cow::Borrowed(data));
405        }
406        // <https://registry.khronos.org/webgl/specs/latest/2.0/#PIXEL_STORE_PARAM_CONSTRAINTS>
407        let data_store_width = if layout.row_length > 0 {
408            layout.row_length
409        } else {
410            width
411        };
412        let data_store_height = if layout.image_height > 0 {
413            layout.image_height
414        } else {
415            height
416        };
417        if layout.skip_pixels + width > data_store_width ||
418            (depth.is_some() && layout.skip_rows + height > data_store_height)
419        {
420            self.webgl_error(InvalidOperation);
421            return Err(());
422        }
423        let depth = depth.unwrap_or(1);
424        if width == 0 || height == 0 || depth == 0 {
425            return Ok(Cow::Borrowed(&[]));
426        }
427
428        let alignment = self.texture_unpacking_alignment.get() as usize;
429        let align = |bytes: usize| bytes.div_ceil(alignment).checked_mul(alignment);
430        let (width, height, depth, cpp) = (
431            width as usize,
432            height as usize,
433            depth as usize,
434            cpp as usize,
435        );
436        let row_pixels = if layout.row_length > 0 {
437            layout.row_length as usize
438        } else {
439            width
440        };
441        let image_rows = if layout.image_height > 0 {
442            layout.image_height as usize
443        } else {
444            height
445        };
446        let row_bytes = width * cpp;
447        // The layout values are bounded only by `i32::MAX`, so the source offsets can overflow.
448        let source = (|| {
449            let src_stride = align(row_pixels.checked_mul(cpp)?)?;
450            let src_image = src_stride.checked_mul(image_rows)?;
451            let start = (layout.skip_images as usize)
452                .checked_mul(src_image)?
453                .checked_add((layout.skip_rows as usize).checked_mul(src_stride)?)?
454                .checked_add((layout.skip_pixels as usize).checked_mul(cpp)?)?;
455            let required = start
456                .checked_add((depth - 1).checked_mul(src_image)?)?
457                .checked_add((height - 1).checked_mul(src_stride)?)?
458                .checked_add(row_bytes)?;
459            (required <= data.len()).then_some((src_stride, src_image, start, align(row_bytes)?))
460        })();
461        let Some((src_stride, src_image, start, dst_stride)) = source else {
462            self.webgl_error(InvalidOperation);
463            return Err(());
464        };
465
466        let mut block = vec![0; dst_stride * height * depth];
467        for image in 0..depth {
468            for row in 0..height {
469                let src = start + image * src_image + row * src_stride;
470                let dst = (image * height + row) * dst_stride;
471                block[dst..dst + row_bytes].copy_from_slice(&data[src..src + row_bytes]);
472            }
473        }
474        Ok(Cow::Owned(block))
475    }
476
477    pub(crate) fn bound_draw_framebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> {
478        self.bound_draw_framebuffer.get()
479    }
480
481    pub(crate) fn current_vao(&self, cx: &mut JSContext) -> DomRoot<WebGLVertexArrayObjectOES> {
482        self.current_vao.or_init(|| {
483            DomRoot::from_ref(
484                self.default_vao
485                    .init_once(|| WebGLVertexArrayObjectOES::new(cx, self, None)),
486            )
487        })
488    }
489
490    pub(crate) fn current_vao_webgl2(&self, cx: &mut JSContext) -> DomRoot<WebGLVertexArrayObject> {
491        self.current_vao_webgl2.or_init(|| {
492            DomRoot::from_ref(
493                self.default_vao_webgl2
494                    .init_once(|| WebGLVertexArrayObject::new(cx, self, None)),
495            )
496        })
497    }
498
499    pub(crate) fn current_vertex_attribs(&self) -> RefMut<'_, Box<[VertexAttrib]>> {
500        self.current_vertex_attribs.borrow_mut()
501    }
502
503    #[inline]
504    pub(crate) fn sender(&self) -> &WebGLMsgSender {
505        &self.droppable.webgl_sender
506    }
507
508    #[inline]
509    pub(crate) fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
510        let result = self
511            .droppable
512            .webgl_sender
513            .send(command, capture_webgl_backtrace());
514        if matches!(fallibility, Operation::Infallible) {
515            result.expect("Operation failed");
516        }
517    }
518
519    #[inline]
520    pub(crate) fn send_command(&self, command: WebGLCommand) {
521        self.send_with_fallibility(command, Operation::Infallible);
522    }
523
524    pub(crate) fn send_command_ignored(&self, command: WebGLCommand) {
525        self.send_with_fallibility(command, Operation::Fallible);
526    }
527
528    #[track_caller]
529    pub(crate) fn webgl_error(&self, err: WebGLError) {
530        // TODO(emilio): Add useful debug messages to this
531        warn!(
532            "WebGL error: {:?}, previous error was {:?}, caller: {}",
533            err,
534            self.last_error.get(),
535            std::panic::Location::caller()
536        );
537
538        // If an error has been detected no further errors must be
539        // recorded until `getError` has been called
540        if self.last_error.get().is_none() {
541            self.last_error.set(Some(err));
542        }
543    }
544
545    // Helper function for validating framebuffer completeness in
546    // calls touching the framebuffer.  From the GLES 2.0.25 spec,
547    // page 119:
548    //
549    //    "Effects of Framebuffer Completeness on Framebuffer
550    //     Operations
551    //
552    //     If the currently bound framebuffer is not framebuffer
553    //     complete, then it is an error to attempt to use the
554    //     framebuffer for writing or reading. This means that
555    //     rendering commands such as DrawArrays and DrawElements, as
556    //     well as commands that read the framebuffer such as
557    //     ReadPixels and CopyTexSubImage, will generate the error
558    //     INVALID_FRAMEBUFFER_OPERATION if called while the
559    //     framebuffer is not framebuffer complete."
560    //
561    // The WebGL spec mentions a couple more operations that trigger
562    // this: clear() and getParameter(IMPLEMENTATION_COLOR_READ_*).
563    pub(crate) fn validate_framebuffer(&self) -> WebGLResult<()> {
564        match self.bound_draw_framebuffer.get() {
565            Some(fb) => match fb.check_status_for_rendering() {
566                CompleteForRendering::Complete => Ok(()),
567                CompleteForRendering::Incomplete => Err(InvalidFramebufferOperation),
568            },
569            None => Ok(()),
570        }
571    }
572
573    /// Validates the bound framebuffer for an operation that reads its color; returns
574    /// `INVALID_OPERATION` when the framebuffer has no color attachment.
575    ///
576    /// <https://registry.khronos.org/webgl/specs/latest/1.0/#READING_FROM_MISSING_ATTACHMENT>
577    pub(crate) fn validate_read_framebuffer(&self) -> WebGLResult<()> {
578        self.validate_framebuffer()?;
579        match self.bound_draw_framebuffer.get() {
580            Some(framebuffer) if !framebuffer.has_color_attachment() => Err(InvalidOperation),
581            _ => Ok(()),
582        }
583    }
584
585    pub(crate) fn validate_ownership<T>(&self, object: &T) -> WebGLResult<()>
586    where
587        T: DerivedFrom<WebGLObject>,
588    {
589        let Some(context) = object.upcast().context() else {
590            return Err(WebGLError::InvalidOperation);
591        };
592        if self != &*context {
593            return Err(WebGLError::InvalidOperation);
594        }
595        Ok(())
596    }
597
598    pub(crate) fn with_location<F>(&self, location: Option<&WebGLUniformLocation>, f: F)
599    where
600        F: FnOnce(&WebGLUniformLocation) -> WebGLResult<()>,
601    {
602        let location = match location {
603            Some(loc) => loc,
604            None => return,
605        };
606        match self.current_program.get() {
607            Some(ref program)
608                if program.id() == location.program_id() &&
609                    program.link_generation() == location.link_generation() => {},
610            _ => return self.webgl_error(InvalidOperation),
611        }
612        if let Err(error) = f(location) {
613            self.webgl_error(error);
614        }
615    }
616
617    pub(crate) fn textures(&self) -> &Textures {
618        &self.textures
619    }
620
621    fn tex_parameter(&self, target: u32, param: u32, value: TexParameterValue) {
622        let texture_slot = match self
623            .textures
624            .active_texture_slot(target, self.webgl_version())
625        {
626            Ok(texture_slot) => texture_slot,
627            Err(error) => return self.webgl_error(error),
628        };
629        let Some(texture) = texture_slot.get() else {
630            return self.webgl_error(InvalidOperation);
631        };
632
633        if !self
634            .extension_manager
635            .is_get_tex_parameter_name_enabled(param)
636        {
637            return self.webgl_error(InvalidEnum);
638        }
639
640        if let Err(error) = texture.tex_parameter(param, value) {
641            return self.webgl_error(error);
642        }
643
644        // Validate non filterable TEXTURE_2D data_types
645        if target != constants::TEXTURE_2D {
646            return;
647        }
648
649        let target = TexImageTarget::Texture2D;
650        if let Some(info) = texture.image_info_for_target(&target, 0) {
651            self.validate_filterable_texture(
652                &texture,
653                target,
654                0,
655                info.internal_format(),
656                Size2D::new(info.width(), info.height()),
657                info.data_type().unwrap_or(TexDataType::UnsignedByte),
658            );
659        }
660    }
661
662    fn vertex_attrib(&self, indx: u32, x: f32, y: f32, z: f32, w: f32) {
663        if indx >= self.limits.max_vertex_attribs {
664            return self.webgl_error(InvalidValue);
665        }
666
667        self.current_vertex_attribs.borrow_mut()[indx as usize] = VertexAttrib::Float(x, y, z, w);
668
669        self.send_command(WebGLCommand::VertexAttrib(indx, x, y, z, w));
670    }
671
672    pub(crate) fn get_current_framebuffer_size(&self) -> Option<(i32, i32)> {
673        match self.bound_draw_framebuffer.get() {
674            Some(fb) => fb.size(),
675
676            // The window system framebuffer is bound
677            None => Some((self.DrawingBufferWidth(), self.DrawingBufferHeight())),
678        }
679    }
680
681    pub(crate) fn get_texture_packing_alignment(&self) -> u8 {
682        self.texture_packing_alignment.get()
683    }
684
685    pub(crate) fn get_current_unpack_state(
686        &self,
687        premultiplied: Alpha,
688    ) -> (Option<AlphaTreatment>, YAxisTreatment) {
689        let settings = self.texture_unpacking_settings.get();
690        let dest_premultiplied = settings.contains(TextureUnpacking::PREMULTIPLY_ALPHA);
691
692        let alpha_treatment = match (premultiplied, dest_premultiplied) {
693            (Alpha::Premultiplied, false) => Some(AlphaTreatment::Unmultiply),
694            (Alpha::NotPremultiplied, true) => Some(AlphaTreatment::Premultiply),
695            _ => None,
696        };
697
698        let y_axis_treatment = if settings.contains(TextureUnpacking::FLIP_Y_AXIS) {
699            YAxisTreatment::Flipped
700        } else {
701            YAxisTreatment::AsIs
702        };
703
704        (alpha_treatment, y_axis_treatment)
705    }
706
707    // LINEAR filtering may be forbidden when using WebGL extensions.
708    /// <https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/>
709    fn validate_filterable_texture(
710        &self,
711        texture: &WebGLTexture,
712        target: TexImageTarget,
713        level: u32,
714        internal_format: TexFormat,
715        size: Size2D<u32>,
716        data_type: TexDataType,
717    ) -> bool {
718        if self
719            .extension_manager
720            .is_filterable(data_type.as_gl_constant()) ||
721            !texture.is_using_linear_filtering()
722        {
723            return true;
724        }
725
726        // Handle validation failed: LINEAR filtering not valid for this texture
727        // WebGL Conformance tests expect to fallback to [0, 0, 0, 255] RGBA UNSIGNED_BYTE
728        let data_type = TexDataType::UnsignedByte;
729        let expected_byte_length = size.area() * 4;
730        let mut pixels = vec![0u8; expected_byte_length as usize];
731        for rgba8 in pixels.chunks_mut(4) {
732            rgba8[3] = 255u8;
733        }
734
735        // TODO(nox): AFAICT here we construct a RGBA8 array and then we
736        // convert it to whatever actual format we need, we should probably
737        // construct the desired format from the start.
738        self.tex_image_2d(
739            texture,
740            target,
741            data_type,
742            internal_format,
743            internal_format.to_unsized(),
744            level,
745            0,
746            1,
747            size,
748            TexSource::Pixels(TexPixels::new(
749                GenericSharedMemory::from_vec(pixels),
750                size,
751                PixelFormat::RGBA8,
752                None,
753                YAxisTreatment::AsIs,
754            )),
755        );
756
757        false
758    }
759
760    fn validate_stencil_actions(&self, action: u32) -> bool {
761        matches!(
762            action,
763            0 | constants::KEEP |
764                constants::REPLACE |
765                constants::INCR |
766                constants::DECR |
767                constants::INVERT |
768                constants::INCR_WRAP |
769                constants::DECR_WRAP
770        )
771    }
772
773    pub(crate) fn get_image_pixels(
774        &self,
775        no_gc: &NoGC,
776        source: TexImageSource,
777    ) -> Fallible<Option<TexPixels>> {
778        Ok(Some(match source {
779            TexImageSource::ImageBitmap(bitmap) => {
780                if !bitmap.origin_is_clean() {
781                    return Err(Error::Security(None));
782                }
783
784                let Some(snapshot) = bitmap.bitmap_data().clone() else {
785                    return Ok(None);
786                };
787
788                let snapshot = snapshot.into_shared();
789                let size = snapshot.size().cast();
790                let format = match snapshot.format() {
791                    SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
792                    SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
793                };
794
795                // If the TexImageSource is an ImageBitmap, the values of
796                // UNPACK_FLIP_Y, UNPACK_PREMULTIPLY_ALPHA, and
797                // UNPACK_COLORSPACE_CONVERSION are to be ignored.
798                // Set alpha and y_axis treatment parameters such that no
799                // conversions will be made.
800                // <https://registry.khronos.org/webgl/specs/latest/1.0/#6.10>
801                TexPixels::new(
802                    snapshot.shared_memory(),
803                    size,
804                    format,
805                    None,
806                    YAxisTreatment::AsIs,
807                )
808            },
809            TexImageSource::ImageData(image_data) => {
810                let (alpha_treatment, y_axis_treatment) =
811                    self.get_current_unpack_state(Alpha::NotPremultiplied);
812
813                TexPixels::new(
814                    image_data.to_shared_memory(no_gc),
815                    image_data.get_size(),
816                    PixelFormat::RGBA8,
817                    alpha_treatment,
818                    y_axis_treatment,
819                )
820            },
821            TexImageSource::HTMLImageElement(image) => {
822                let document = match self.canvas {
823                    HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => {
824                        canvas.owner_document()
825                    },
826                    HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(ref _canvas) => {
827                        // TODO: Support retrieving image pixels here for OffscreenCanvas
828                        return Ok(None);
829                    },
830                };
831                // `same_origin` is false for an image that has not loaded yet.
832                if !image.is_usable().is_ok_and(|usable| usable) {
833                    return Ok(None);
834                }
835
836                if !image.same_origin(&document.origin()) {
837                    return Err(Error::Security(None));
838                }
839
840                // Vector images are not currently supported here and there are
841                // some open questions in the specification about how to handle them:
842                // See https://github.com/KhronosGroup/WebGL/issues/1503
843                let Some(snapshot) = image.get_raster_image_data() else {
844                    return Ok(None);
845                };
846
847                let snapshot = snapshot.into_shared();
848                let size = snapshot.size().cast();
849                let format: PixelFormat = match snapshot.format() {
850                    SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
851                    SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
852                };
853
854                let (alpha_treatment, y_axis_treatment) =
855                    self.get_current_unpack_state(snapshot.alpha_mode().alpha());
856
857                TexPixels::new(
858                    snapshot.shared_memory(),
859                    size,
860                    format,
861                    alpha_treatment,
862                    y_axis_treatment,
863                )
864            },
865            // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D,
866            // but we need to refactor it moving it to `HTMLCanvasElement` and support
867            // WebGLContext (probably via GetPixels()).
868            TexImageSource::HTMLCanvasElement(canvas) => {
869                if !canvas.origin_is_clean() {
870                    return Err(Error::Security(None));
871                }
872
873                let Some(snapshot) = canvas.get_image_data() else {
874                    return Ok(None);
875                };
876
877                let snapshot = snapshot.into_shared();
878                let size = snapshot.size().cast();
879                let format = match snapshot.format() {
880                    SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
881                    SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
882                };
883
884                let (alpha_treatment, y_axis_treatment) =
885                    self.get_current_unpack_state(snapshot.alpha_mode().alpha());
886
887                TexPixels::new(
888                    snapshot.shared_memory(),
889                    size,
890                    format,
891                    alpha_treatment,
892                    y_axis_treatment,
893                )
894            },
895            TexImageSource::HTMLVideoElement(video) => {
896                if !video.origin_is_clean() {
897                    return Err(Error::Security(None));
898                }
899
900                let Some(snapshot) = video.get_current_frame_data() else {
901                    return Ok(None);
902                };
903
904                let snapshot = snapshot.into_shared();
905                let size = snapshot.size().cast();
906                let format: PixelFormat = match snapshot.format() {
907                    SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
908                    SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
909                };
910
911                let (alpha_treatment, y_axis_treatment) =
912                    self.get_current_unpack_state(snapshot.alpha_mode().alpha());
913
914                TexPixels::new(
915                    snapshot.shared_memory(),
916                    size,
917                    format,
918                    alpha_treatment,
919                    y_axis_treatment,
920                )
921            },
922        }))
923    }
924
925    // TODO(emilio): Move this logic to a validator.
926    pub(crate) fn validate_tex_image_2d_data(
927        &self,
928        width: u32,
929        height: u32,
930        format: TexFormat,
931        data_type: TexDataType,
932        unpacking_alignment: u32,
933        data: Option<&ArrayBufferView>,
934    ) -> Result<u32, ()> {
935        let element_size = data_type.element_size();
936        let components_per_element = data_type.components_per_element();
937        let components = format.components();
938
939        // If data is non-null, the type of pixels must match the type of the
940        // data to be read.
941        // If it is UNSIGNED_BYTE, a Uint8Array must be supplied;
942        // if it is UNSIGNED_SHORT_5_6_5, UNSIGNED_SHORT_4_4_4_4,
943        // or UNSIGNED_SHORT_5_5_5_1, a Uint16Array must be supplied.
944        // or FLOAT, a Float32Array must be supplied.
945        // If the types do not match, an INVALID_OPERATION error is generated.
946        let data_type_matches = data.as_ref().is_none_or(|buffer| {
947            Some(data_type.sized_data_type()) ==
948                array_buffer_type_to_sized_type(buffer.get_array_type()) &&
949                data_type.required_webgl_version() <= self.webgl_version()
950        });
951
952        if !data_type_matches {
953            self.webgl_error(InvalidOperation);
954            return Err(());
955        }
956
957        // NOTE: width and height are positive or zero due to validate()
958        if height == 0 {
959            Ok(0)
960        } else {
961            // We need to be careful here to not count unpack
962            // alignment at the end of the image, otherwise (for
963            // example) passing a single byte for uploading a 1x1
964            // GL_ALPHA/GL_UNSIGNED_BYTE texture would throw an error.
965            let cpp = element_size * components / components_per_element;
966            let stride = (width * cpp + unpacking_alignment - 1) & !(unpacking_alignment - 1);
967            Ok(stride * (height - 1) + width * cpp)
968        }
969    }
970
971    #[allow(clippy::too_many_arguments)]
972    pub(crate) fn tex_image_2d(
973        &self,
974        texture: &WebGLTexture,
975        target: TexImageTarget,
976        data_type: TexDataType,
977        internal_format: TexFormat,
978        format: TexFormat,
979        level: u32,
980        _border: u32,
981        unpacking_alignment: u32,
982        size: Size2D<u32>,
983        source: TexSource,
984    ) {
985        // TexImage2D depth is always equal to 1.
986        if let Err(error) = texture.initialize(
987            target,
988            size.width,
989            size.height,
990            1,
991            internal_format,
992            level,
993            Some(data_type),
994        ) {
995            self.webgl_error(error);
996        }
997
998        let internal_format = self
999            .extension_manager
1000            .get_effective_tex_internal_format(internal_format, data_type.as_gl_constant());
1001
1002        let effective_data_type = self
1003            .extension_manager
1004            .effective_type(data_type.as_gl_constant());
1005
1006        match source {
1007            TexSource::Pixels(pixels) => {
1008                // TODO(emilio): convert colorspace if requested.
1009                self.send_command(WebGLCommand::TexImage2D {
1010                    target: target.as_gl_constant(),
1011                    level,
1012                    internal_format,
1013                    size,
1014                    format,
1015                    data_type,
1016                    effective_data_type,
1017                    unpacking_alignment,
1018                    alpha_treatment: pixels.alpha_treatment,
1019                    y_axis_treatment: pixels.y_axis_treatment,
1020                    pixel_format: pixels.pixel_format,
1021                    data: pixels.data.into(),
1022                });
1023            },
1024            TexSource::BufferOffset(offset) => {
1025                self.send_command(WebGLCommand::TexImage2DPBO {
1026                    target: target.as_gl_constant(),
1027                    level,
1028                    internal_format,
1029                    size,
1030                    format,
1031                    effective_data_type,
1032                    unpacking_alignment,
1033                    offset,
1034                });
1035            },
1036        }
1037
1038        if let Some(fb) = self.bound_draw_framebuffer.get() {
1039            fb.invalidate_texture(texture);
1040        }
1041    }
1042
1043    #[allow(clippy::too_many_arguments)]
1044    fn tex_sub_image_2d(
1045        &self,
1046        texture: DomRoot<WebGLTexture>,
1047        target: TexImageTarget,
1048        level: u32,
1049        xoffset: i32,
1050        yoffset: i32,
1051        format: TexFormat,
1052        data_type: TexDataType,
1053        unpacking_alignment: u32,
1054        pixels: TexPixels,
1055    ) {
1056        // We have already validated level
1057        let image_info = match texture.image_info_for_target(&target, level) {
1058            Some(info) => info,
1059            None => return self.webgl_error(InvalidOperation),
1060        };
1061
1062        // GL_INVALID_VALUE is generated if:
1063        //   - xoffset or yoffset is less than 0
1064        //   - x offset plus the width is greater than the texture width
1065        //   - y offset plus the height is greater than the texture height
1066        if xoffset < 0 ||
1067            (xoffset as u32 + pixels.size().width) > image_info.width() ||
1068            yoffset < 0 ||
1069            (yoffset as u32 + pixels.size().height) > image_info.height()
1070        {
1071            return self.webgl_error(InvalidValue);
1072        }
1073
1074        // The unsized format must be compatible with the sized internal format
1075        debug_assert!(!format.is_sized());
1076        if format != image_info.internal_format().to_unsized() {
1077            return self.webgl_error(InvalidOperation);
1078        }
1079
1080        // See https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1.6
1081        if self.webgl_version() == WebGLVersion::WebGL1 &&
1082            data_type != image_info.data_type().unwrap()
1083        {
1084            return self.webgl_error(InvalidOperation);
1085        }
1086
1087        let effective_data_type = self
1088            .extension_manager
1089            .effective_type(data_type.as_gl_constant());
1090
1091        // TODO(emilio): convert colorspace if requested.
1092        self.send_command(WebGLCommand::TexSubImage2D {
1093            target: target.as_gl_constant(),
1094            level,
1095            xoffset,
1096            yoffset,
1097            size: pixels.size(),
1098            format,
1099            data_type,
1100            effective_data_type,
1101            unpacking_alignment,
1102            alpha_treatment: pixels.alpha_treatment,
1103            y_axis_treatment: pixels.y_axis_treatment,
1104            pixel_format: pixels.pixel_format,
1105            data: pixels.data.into(),
1106        });
1107    }
1108
1109    fn get_gl_extensions(&self) -> String {
1110        let (sender, receiver) = webgl_channel().unwrap();
1111        self.send_command(WebGLCommand::GetExtensions(sender));
1112        receiver.recv().unwrap()
1113    }
1114
1115    // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
1116    pub(crate) fn draw_arrays_instanced(
1117        &self,
1118        cx: &mut JSContext,
1119        mode: u32,
1120        first: i32,
1121        count: i32,
1122        primcount: i32,
1123    ) -> WebGLResult<()> {
1124        match mode {
1125            constants::POINTS |
1126            constants::LINE_STRIP |
1127            constants::LINE_LOOP |
1128            constants::LINES |
1129            constants::TRIANGLE_STRIP |
1130            constants::TRIANGLE_FAN |
1131            constants::TRIANGLES => {},
1132            _ => {
1133                return Err(InvalidEnum);
1134            },
1135        }
1136        if first < 0 || count < 0 || primcount < 0 {
1137            return Err(InvalidValue);
1138        }
1139
1140        let current_program = self.current_program.get().ok_or(InvalidOperation)?;
1141
1142        let required_len = if count > 0 {
1143            first
1144                .checked_add(count)
1145                .map(|len| len as u32)
1146                .ok_or(InvalidOperation)?
1147        } else {
1148            0
1149        };
1150
1151        match self.webgl_version() {
1152            WebGLVersion::WebGL1 => self.current_vao(cx).validate_for_draw(
1153                required_len,
1154                primcount as u32,
1155                &current_program.active_attribs(),
1156            )?,
1157            WebGLVersion::WebGL2 => self.current_vao_webgl2(cx).validate_for_draw(
1158                required_len,
1159                primcount as u32,
1160                &current_program.active_attribs(),
1161            )?,
1162        };
1163
1164        self.validate_framebuffer()?;
1165
1166        if count == 0 || primcount == 0 {
1167            return Ok(());
1168        }
1169
1170        self.send_command(if primcount == 1 {
1171            WebGLCommand::DrawArrays { mode, first, count }
1172        } else {
1173            WebGLCommand::DrawArraysInstanced {
1174                mode,
1175                first,
1176                count,
1177                primcount,
1178            }
1179        });
1180        self.mark_as_dirty();
1181        Ok(())
1182    }
1183
1184    // https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
1185    pub(crate) fn draw_elements_instanced(
1186        &self,
1187        cx: &mut JSContext,
1188        mode: u32,
1189        count: i32,
1190        type_: u32,
1191        offset: i64,
1192        primcount: i32,
1193    ) -> WebGLResult<()> {
1194        match mode {
1195            constants::POINTS |
1196            constants::LINE_STRIP |
1197            constants::LINE_LOOP |
1198            constants::LINES |
1199            constants::TRIANGLE_STRIP |
1200            constants::TRIANGLE_FAN |
1201            constants::TRIANGLES => {},
1202            _ => {
1203                return Err(InvalidEnum);
1204            },
1205        }
1206        if count < 0 || offset < 0 || primcount < 0 {
1207            return Err(InvalidValue);
1208        }
1209        let type_size = match type_ {
1210            constants::UNSIGNED_BYTE => 1,
1211            constants::UNSIGNED_SHORT => 2,
1212            constants::UNSIGNED_INT => match self.webgl_version() {
1213                WebGLVersion::WebGL1 if self.extension_manager.is_element_index_uint_enabled() => 4,
1214                WebGLVersion::WebGL2 => 4,
1215                _ => return Err(InvalidEnum),
1216            },
1217            _ => return Err(InvalidEnum),
1218        };
1219        if offset % type_size != 0 {
1220            return Err(InvalidOperation);
1221        }
1222
1223        let current_program = self.current_program.get().ok_or(InvalidOperation)?;
1224        let array_buffer = match self.webgl_version() {
1225            WebGLVersion::WebGL1 => self.current_vao(cx).element_array_buffer().get(),
1226            WebGLVersion::WebGL2 => self.current_vao_webgl2(cx).element_array_buffer().get(),
1227        }
1228        .ok_or(InvalidOperation)?;
1229
1230        if count > 0 && primcount > 0 {
1231            // This operation cannot overflow in u64 and we know all those values are nonnegative.
1232            let val = offset as u64 + (count as u64 * type_size as u64);
1233            if val > array_buffer.capacity() as u64 {
1234                return Err(InvalidOperation);
1235            }
1236        }
1237
1238        // TODO(nox): Pass the correct number of vertices required.
1239        match self.webgl_version() {
1240            WebGLVersion::WebGL1 => self.current_vao(cx).validate_for_draw(
1241                0,
1242                primcount as u32,
1243                &current_program.active_attribs(),
1244            )?,
1245            WebGLVersion::WebGL2 => self.current_vao_webgl2(cx).validate_for_draw(
1246                0,
1247                primcount as u32,
1248                &current_program.active_attribs(),
1249            )?,
1250        };
1251
1252        self.validate_framebuffer()?;
1253
1254        if count == 0 || primcount == 0 {
1255            return Ok(());
1256        }
1257
1258        let offset = offset as u32;
1259        self.send_command(if primcount == 1 {
1260            WebGLCommand::DrawElements {
1261                mode,
1262                count,
1263                type_,
1264                offset,
1265            }
1266        } else {
1267            WebGLCommand::DrawElementsInstanced {
1268                mode,
1269                count,
1270                type_,
1271                offset,
1272                primcount,
1273            }
1274        });
1275        self.mark_as_dirty();
1276        Ok(())
1277    }
1278
1279    pub(crate) fn vertex_attrib_divisor(&self, cx: &mut JSContext, index: u32, divisor: u32) {
1280        if index >= self.limits.max_vertex_attribs {
1281            return self.webgl_error(InvalidValue);
1282        }
1283
1284        match self.webgl_version() {
1285            WebGLVersion::WebGL1 => self.current_vao(cx).vertex_attrib_divisor(index, divisor),
1286            WebGLVersion::WebGL2 => self
1287                .current_vao_webgl2(cx)
1288                .vertex_attrib_divisor(index, divisor),
1289        };
1290        self.send_command(WebGLCommand::VertexAttribDivisor { index, divisor });
1291    }
1292
1293    pub(crate) fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> {
1294        self.bound_buffer_array.get()
1295    }
1296
1297    pub(crate) fn array_buffer_slot(&self) -> &MutNullableDom<WebGLBuffer> {
1298        &self.bound_buffer_array
1299    }
1300
1301    pub(crate) fn bound_buffer(
1302        &self,
1303        cx: &mut JSContext,
1304        target: u32,
1305    ) -> WebGLResult<Option<DomRoot<WebGLBuffer>>> {
1306        match target {
1307            constants::ARRAY_BUFFER => Ok(self.bound_buffer_array.get()),
1308            constants::ELEMENT_ARRAY_BUFFER => {
1309                Ok(self.current_vao(cx).element_array_buffer().get())
1310            },
1311            _ => Err(WebGLError::InvalidEnum),
1312        }
1313    }
1314
1315    pub(crate) fn buffer_usage(&self, usage: u32) -> WebGLResult<u32> {
1316        match usage {
1317            constants::STREAM_DRAW | constants::STATIC_DRAW | constants::DYNAMIC_DRAW => Ok(usage),
1318            _ => Err(WebGLError::InvalidEnum),
1319        }
1320    }
1321
1322    pub(crate) fn create_vertex_array(
1323        &self,
1324        cx: &mut JSContext,
1325    ) -> Option<DomRoot<WebGLVertexArrayObjectOES>> {
1326        let (sender, receiver) = webgl_channel().unwrap();
1327        self.send_command(WebGLCommand::CreateVertexArray(sender));
1328        receiver
1329            .recv()
1330            .unwrap()
1331            .map(|id| WebGLVertexArrayObjectOES::new(cx, self, Some(id)))
1332    }
1333
1334    pub(crate) fn create_vertex_array_webgl2(
1335        &self,
1336        cx: &mut JSContext,
1337    ) -> Option<DomRoot<WebGLVertexArrayObject>> {
1338        let (sender, receiver) = webgl_channel().unwrap();
1339        self.send_command(WebGLCommand::CreateVertexArray(sender));
1340        receiver
1341            .recv()
1342            .unwrap()
1343            .map(|id| WebGLVertexArrayObject::new(cx, self, Some(id)))
1344    }
1345
1346    pub(crate) fn delete_vertex_array(
1347        &self,
1348        cx: &mut JSContext,
1349        vao: Option<&WebGLVertexArrayObjectOES>,
1350    ) {
1351        if let Some(vao) = vao {
1352            if let Err(error) = self.validate_ownership(vao) {
1353                return self.webgl_error(error);
1354            }
1355            // The default vertex array has no id and should never be passed around.
1356            assert!(vao.id().is_some());
1357            if vao.is_deleted() {
1358                return;
1359            }
1360            if vao == &*self.current_vao(cx) {
1361                // Setting it to None will make self.current_vao() reset it to the default one
1362                // next time it is called.
1363                self.current_vao.set(None);
1364                self.send_command(WebGLCommand::BindVertexArray(None));
1365            }
1366            vao.delete(Operation::Infallible);
1367        }
1368    }
1369
1370    pub(crate) fn delete_vertex_array_webgl2(
1371        &self,
1372        cx: &mut JSContext,
1373        vao: Option<&WebGLVertexArrayObject>,
1374    ) {
1375        if let Some(vao) = vao {
1376            if let Err(error) = self.validate_ownership(vao) {
1377                return self.webgl_error(error);
1378            }
1379            // The default vertex array has no id and should never be passed around.
1380            assert!(vao.id().is_some());
1381            if vao.is_deleted() {
1382                return;
1383            }
1384            if vao == &*self.current_vao_webgl2(cx) {
1385                // Setting it to None will make self.current_vao() reset it to the default one
1386                // next time it is called.
1387                self.current_vao_webgl2.set(None);
1388                self.send_command(WebGLCommand::BindVertexArray(None));
1389            }
1390            vao.delete(Operation::Infallible);
1391        }
1392    }
1393
1394    pub(crate) fn is_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) -> bool {
1395        vao.is_some_and(|vao| {
1396            // The default vertex array has no id and should never be passed around.
1397            assert!(vao.id().is_some());
1398            self.validate_ownership(vao).is_ok() && vao.ever_bound() && !vao.is_deleted()
1399        })
1400    }
1401
1402    pub(crate) fn is_vertex_array_webgl2(&self, vao: Option<&WebGLVertexArrayObject>) -> bool {
1403        vao.is_some_and(|vao| {
1404            // The default vertex array has no id and should never be passed around.
1405            assert!(vao.id().is_some());
1406            self.validate_ownership(vao).is_ok() && vao.ever_bound() && !vao.is_deleted()
1407        })
1408    }
1409
1410    pub(crate) fn bind_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) {
1411        if let Some(vao) = vao {
1412            // The default vertex array has no id and should never be passed around.
1413            assert!(vao.id().is_some());
1414            if let Err(error) = self.validate_ownership(vao) {
1415                return self.webgl_error(error);
1416            }
1417            if vao.is_deleted() {
1418                return self.webgl_error(InvalidOperation);
1419            }
1420            vao.set_ever_bound();
1421        }
1422        self.send_command(WebGLCommand::BindVertexArray(vao.and_then(|vao| vao.id())));
1423        // Setting it to None will make self.current_vao() reset it to the default one
1424        // next time it is called.
1425        self.current_vao.set(vao);
1426    }
1427
1428    pub(crate) fn bind_vertex_array_webgl2(&self, vao: Option<&WebGLVertexArrayObject>) {
1429        if let Some(vao) = vao {
1430            // The default vertex array has no id and should never be passed around.
1431            assert!(vao.id().is_some());
1432            if let Err(error) = self.validate_ownership(vao) {
1433                return self.webgl_error(error);
1434            }
1435            if vao.is_deleted() {
1436                return self.webgl_error(InvalidOperation);
1437            }
1438            vao.set_ever_bound();
1439        }
1440        self.send_command(WebGLCommand::BindVertexArray(vao.and_then(|vao| vao.id())));
1441        // Setting it to None will make self.current_vao() reset it to the default one
1442        // next time it is called.
1443        self.current_vao_webgl2.set(vao);
1444    }
1445
1446    fn validate_blend_mode(&self, mode: u32) -> WebGLResult<()> {
1447        match mode {
1448            constants::FUNC_ADD | constants::FUNC_SUBTRACT | constants::FUNC_REVERSE_SUBTRACT => {
1449                Ok(())
1450            },
1451            EXTBlendMinmaxConstants::MIN_EXT | EXTBlendMinmaxConstants::MAX_EXT
1452                if self.extension_manager.is_blend_minmax_enabled() =>
1453            {
1454                Ok(())
1455            },
1456            _ => Err(InvalidEnum),
1457        }
1458    }
1459
1460    pub(crate) fn initialize_framebuffer(&self, clear_bits: u32) {
1461        if clear_bits == 0 {
1462            return;
1463        }
1464        self.send_command(WebGLCommand::InitializeFramebuffer {
1465            color: clear_bits & constants::COLOR_BUFFER_BIT != 0,
1466            depth: clear_bits & constants::DEPTH_BUFFER_BIT != 0,
1467            stencil: clear_bits & constants::STENCIL_BUFFER_BIT != 0,
1468        });
1469    }
1470
1471    pub(crate) fn extension_manager(&self) -> &WebGLExtensions {
1472        &self.extension_manager
1473    }
1474
1475    pub(crate) fn buffer_data(
1476        &self,
1477        no_gc: &NoGC,
1478        target: u32,
1479        data: Option<ArrayBufferViewOrArrayBuffer>,
1480        usage: u32,
1481        bound_buffer: Option<DomRoot<WebGLBuffer>>,
1482    ) {
1483        let Some(data) = data else {
1484            return self.webgl_error(InvalidValue);
1485        };
1486        let Some(bound_buffer) = bound_buffer else {
1487            return self.webgl_error(InvalidOperation);
1488        };
1489
1490        let data = get_buffer_source_slice(&data, no_gc);
1491        if let Err(error) = bound_buffer.buffer_data(target, data, usage) {
1492            self.webgl_error(error);
1493        }
1494    }
1495
1496    pub(crate) fn buffer_data_(
1497        &self,
1498        target: u32,
1499        size: i64,
1500        usage: u32,
1501        bound_buffer: Option<DomRoot<WebGLBuffer>>,
1502    ) {
1503        let Some(bound_buffer) = bound_buffer else {
1504            return self.webgl_error(InvalidOperation);
1505        };
1506
1507        if size < 0 {
1508            return self.webgl_error(InvalidValue);
1509        }
1510
1511        // FIXME: Allocating a buffer based on user-requested size is
1512        // not great, but we don't have a fallible allocation to try.
1513        let data = vec![0u8; size as usize];
1514        if let Err(error) = bound_buffer.buffer_data(target, &data, usage) {
1515            self.webgl_error(error);
1516        }
1517    }
1518
1519    pub(crate) fn buffer_sub_data(
1520        &self,
1521        no_gc: &NoGC,
1522        target: u32,
1523        offset: i64,
1524        data: ArrayBufferViewOrArrayBuffer,
1525        bound_buffer: Option<DomRoot<WebGLBuffer>>,
1526    ) {
1527        let Some(bound_buffer) = bound_buffer else {
1528            return self.webgl_error(InvalidOperation);
1529        };
1530
1531        if offset < 0 {
1532            return self.webgl_error(InvalidValue);
1533        }
1534
1535        let data = get_buffer_source_slice(&data, no_gc);
1536        if (offset as u64) + data.len() as u64 > bound_buffer.capacity() as u64 {
1537            return self.webgl_error(InvalidValue);
1538        }
1539        let (sender, receiver) = generic_channel::channel().unwrap();
1540        self.send_command(WebGLCommand::BufferSubData(
1541            target,
1542            offset as isize,
1543            receiver,
1544        ));
1545        let buffer = GenericSharedMemory::from_bytes(data);
1546        sender.send(buffer).unwrap();
1547    }
1548
1549    pub(crate) fn bind_buffer_maybe(
1550        &self,
1551        slot: &MutNullableDom<WebGLBuffer>,
1552        target: u32,
1553        buffer: Option<&WebGLBuffer>,
1554    ) {
1555        if let Some(buffer) = buffer {
1556            if let Err(error) = self.validate_ownership(buffer) {
1557                return self.webgl_error(error);
1558            }
1559
1560            if buffer.is_marked_for_deletion() {
1561                return self.webgl_error(InvalidOperation);
1562            }
1563            if let Err(error) = buffer.set_target_maybe(target) {
1564                return self.webgl_error(error);
1565            }
1566            buffer.increment_attached_counter();
1567        }
1568
1569        self.send_command(WebGLCommand::BindBuffer(target, buffer.map(|b| b.id())));
1570        if let Some(old) = slot.get() {
1571            old.decrement_attached_counter(Operation::Infallible);
1572        }
1573
1574        slot.set(buffer);
1575    }
1576
1577    pub(crate) fn current_program(&self) -> Option<DomRoot<WebGLProgram>> {
1578        self.current_program.get()
1579    }
1580
1581    pub(crate) fn uniform_check_program(
1582        &self,
1583        program: &WebGLProgram,
1584        location: &WebGLUniformLocation,
1585    ) -> WebGLResult<()> {
1586        self.validate_ownership(program)?;
1587
1588        if program.is_deleted() ||
1589            !program.is_linked() ||
1590            self.context_id() != location.context_id() ||
1591            program.id() != location.program_id() ||
1592            program.link_generation() != location.link_generation()
1593        {
1594            return Err(InvalidOperation);
1595        }
1596
1597        Ok(())
1598    }
1599
1600    fn uniform_vec_section_int(
1601        &self,
1602        vec: Int32ArrayOrLongSequence,
1603        offset: u32,
1604        length: u32,
1605        uniform_size: usize,
1606        uniform_location: &WebGLUniformLocation,
1607    ) -> WebGLResult<Vec<i32>> {
1608        let vec = match vec {
1609            Int32ArrayOrLongSequence::Int32Array(v) => v.to_vec().unwrap_or_default(),
1610            Int32ArrayOrLongSequence::LongSequence(v) => v,
1611        };
1612        self.uniform_vec_section::<i32>(vec, offset, length, uniform_size, uniform_location)
1613    }
1614
1615    fn uniform_vec_section_float(
1616        &self,
1617        vec: Float32ArrayOrUnrestrictedFloatSequence,
1618        offset: u32,
1619        length: u32,
1620        uniform_size: usize,
1621        uniform_location: &WebGLUniformLocation,
1622    ) -> WebGLResult<Vec<f32>> {
1623        let vec = match vec {
1624            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => {
1625                v.to_vec().unwrap_or_default()
1626            },
1627            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
1628        };
1629        self.uniform_vec_section::<f32>(vec, offset, length, uniform_size, uniform_location)
1630    }
1631
1632    pub(crate) fn uniform_vec_section<T: Clone>(
1633        &self,
1634        vec: Vec<T>,
1635        offset: u32,
1636        length: u32,
1637        uniform_size: usize,
1638        uniform_location: &WebGLUniformLocation,
1639    ) -> WebGLResult<Vec<T>> {
1640        let offset = offset as usize;
1641        if offset > vec.len() {
1642            return Err(InvalidValue);
1643        }
1644
1645        let length = if length > 0 {
1646            length as usize
1647        } else {
1648            vec.len() - offset
1649        };
1650        if offset + length > vec.len() {
1651            return Err(InvalidValue);
1652        }
1653
1654        let vec = if offset == 0 && length == vec.len() {
1655            vec
1656        } else {
1657            vec[offset..offset + length].to_vec()
1658        };
1659
1660        if vec.len() < uniform_size || vec.len() % uniform_size != 0 {
1661            return Err(InvalidValue);
1662        }
1663        if uniform_location.size().is_none() && vec.len() != uniform_size {
1664            return Err(InvalidOperation);
1665        }
1666
1667        Ok(vec)
1668    }
1669
1670    pub(crate) fn uniform_matrix_section(
1671        &self,
1672        vec: Float32ArrayOrUnrestrictedFloatSequence,
1673        offset: u32,
1674        length: u32,
1675        transpose: bool,
1676        uniform_size: usize,
1677        uniform_location: &WebGLUniformLocation,
1678    ) -> WebGLResult<Vec<f32>> {
1679        let vec = match vec {
1680            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => {
1681                v.to_vec().unwrap_or_default()
1682            },
1683            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
1684        };
1685        if transpose {
1686            return Err(InvalidValue);
1687        }
1688        self.uniform_vec_section::<f32>(vec, offset, length, uniform_size, uniform_location)
1689    }
1690
1691    pub(crate) fn get_draw_framebuffer_slot(&self) -> &MutNullableDom<WebGLFramebuffer> {
1692        &self.bound_draw_framebuffer
1693    }
1694
1695    pub(crate) fn get_read_framebuffer_slot(&self) -> &MutNullableDom<WebGLFramebuffer> {
1696        &self.bound_read_framebuffer
1697    }
1698
1699    pub(crate) fn validate_new_framebuffer_binding(
1700        &self,
1701        framebuffer: Option<&WebGLFramebuffer>,
1702    ) -> WebGLResult<()> {
1703        if let Some(fb) = framebuffer {
1704            self.validate_ownership(fb)?;
1705            if fb.is_deleted() {
1706                // From the WebGL spec:
1707                //
1708                //     "An attempt to bind a deleted framebuffer will
1709                //      generate an INVALID_OPERATION error, and the
1710                //      current binding will remain untouched."
1711                return Err(InvalidOperation);
1712            }
1713        }
1714        Ok(())
1715    }
1716
1717    pub(crate) fn bind_framebuffer_to(
1718        &self,
1719        target: u32,
1720        framebuffer: Option<&WebGLFramebuffer>,
1721        slot: &MutNullableDom<WebGLFramebuffer>,
1722    ) {
1723        match framebuffer {
1724            Some(framebuffer) => framebuffer.bind(target),
1725            None => {
1726                // Bind the default framebuffer
1727                let cmd =
1728                    WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Default);
1729                self.send_command(cmd);
1730            },
1731        }
1732        slot.set(framebuffer);
1733    }
1734
1735    pub(crate) fn renderbuffer_storage(
1736        &self,
1737        target: u32,
1738        samples: i32,
1739        internal_format: u32,
1740        width: i32,
1741        height: i32,
1742    ) {
1743        if target != constants::RENDERBUFFER {
1744            return self.webgl_error(InvalidEnum);
1745        }
1746
1747        let max = self.limits.max_renderbuffer_size;
1748
1749        if samples < 0 || width < 0 || width as u32 > max || height < 0 || height as u32 > max {
1750            return self.webgl_error(InvalidValue);
1751        }
1752
1753        let Some(rb) = self.bound_renderbuffer.get() else {
1754            return self.webgl_error(InvalidOperation);
1755        };
1756
1757        if let Err(error) = rb.storage(samples, internal_format, width, height) {
1758            self.webgl_error(error);
1759        }
1760
1761        if let Some(fb) = self.bound_draw_framebuffer.get() {
1762            fb.invalidate_renderbuffer(&rb);
1763        }
1764
1765        // FIXME: https://github.com/servo/servo/issues/13710
1766    }
1767
1768    pub(crate) fn valid_color_attachment_enum(&self, attachment: u32) -> bool {
1769        let last_slot = constants::COLOR_ATTACHMENT0 + self.limits().max_color_attachments - 1;
1770        constants::COLOR_ATTACHMENT0 <= attachment && attachment <= last_slot
1771    }
1772
1773    #[allow(clippy::too_many_arguments)]
1774    pub(crate) fn compressed_tex_image_2d(
1775        &self,
1776        target: u32,
1777        level: i32,
1778        internal_format: u32,
1779        width: i32,
1780        height: i32,
1781        border: i32,
1782        data: &[u8],
1783    ) {
1784        let validator = CompressedTexImage2DValidator::new(
1785            self,
1786            target,
1787            level,
1788            width,
1789            height,
1790            border,
1791            internal_format,
1792            data.len(),
1793        );
1794        let CommonCompressedTexImage2DValidatorResult {
1795            texture,
1796            target,
1797            level,
1798            width,
1799            height,
1800            compression,
1801        } = match validator.validate() {
1802            Ok(result) => result,
1803            Err(_) => return,
1804        };
1805
1806        if texture.is_immutable() {
1807            return self.webgl_error(InvalidOperation);
1808        }
1809
1810        let size = Size2D::new(width, height);
1811        let data = GenericSharedMemory::from_bytes(data);
1812
1813        if let Err(error) = texture.initialize(
1814            target,
1815            size.width,
1816            size.height,
1817            1,
1818            compression.format,
1819            level,
1820            Some(TexDataType::UnsignedByte),
1821        ) {
1822            self.webgl_error(error);
1823        }
1824
1825        self.send_command(WebGLCommand::CompressedTexImage2D {
1826            target: target.as_gl_constant(),
1827            level,
1828            internal_format,
1829            size: Size2D::new(width, height),
1830            data: data.into(),
1831        });
1832
1833        if let Some(fb) = self.bound_draw_framebuffer.get() {
1834            fb.invalidate_texture(&texture);
1835        }
1836    }
1837
1838    #[allow(clippy::too_many_arguments)]
1839    pub(crate) fn compressed_tex_sub_image_2d(
1840        &self,
1841        target: u32,
1842        level: i32,
1843        xoffset: i32,
1844        yoffset: i32,
1845        width: i32,
1846        height: i32,
1847        format: u32,
1848        data: &[u8],
1849    ) {
1850        let validator = CompressedTexSubImage2DValidator::new(
1851            self,
1852            target,
1853            level,
1854            xoffset,
1855            yoffset,
1856            width,
1857            height,
1858            format,
1859            data.len(),
1860        );
1861        let CommonCompressedTexImage2DValidatorResult {
1862            texture: _,
1863            target,
1864            level,
1865            width,
1866            height,
1867            ..
1868        } = match validator.validate() {
1869            Ok(result) => result,
1870            Err(_) => return,
1871        };
1872
1873        let data = GenericSharedMemory::from_bytes(data);
1874
1875        self.send_command(WebGLCommand::CompressedTexSubImage2D {
1876            target: target.as_gl_constant(),
1877            level: level as i32,
1878            xoffset,
1879            yoffset,
1880            size: Size2D::new(width, height),
1881            format,
1882            data: data.into(),
1883        });
1884    }
1885
1886    pub(crate) fn uniform1iv(
1887        &self,
1888        location: Option<&WebGLUniformLocation>,
1889        val: Int32ArrayOrLongSequence,
1890        src_offset: u32,
1891        src_length: u32,
1892    ) {
1893        self.with_location(location, |location| {
1894            match location.type_() {
1895                constants::BOOL |
1896                constants::INT |
1897                constants::SAMPLER_2D |
1898                WebGL2RenderingContextConstants::SAMPLER_2D_ARRAY |
1899                WebGL2RenderingContextConstants::SAMPLER_3D |
1900                constants::SAMPLER_CUBE => {},
1901                _ => return Err(InvalidOperation),
1902            }
1903
1904            let val = self.uniform_vec_section_int(val, src_offset, src_length, 1, location)?;
1905
1906            match location.type_() {
1907                constants::SAMPLER_2D |
1908                constants::SAMPLER_CUBE |
1909                WebGL2RenderingContextConstants::SAMPLER_2D_ARRAY |
1910                WebGL2RenderingContextConstants::SAMPLER_3D => {
1911                    for &v in val
1912                        .iter()
1913                        .take(cmp::min(location.size().unwrap_or(1) as usize, val.len()))
1914                    {
1915                        if v < 0 || v as u32 >= self.limits.max_combined_texture_image_units {
1916                            return Err(InvalidValue);
1917                        }
1918                    }
1919                },
1920                _ => {},
1921            }
1922            self.send_command(WebGLCommand::Uniform1iv(location.id(), val));
1923            Ok(())
1924        });
1925    }
1926
1927    pub(crate) fn uniform1fv(
1928        &self,
1929        location: Option<&WebGLUniformLocation>,
1930        val: Float32ArrayOrUnrestrictedFloatSequence,
1931        src_offset: u32,
1932        src_length: u32,
1933    ) {
1934        self.with_location(location, |location| {
1935            match location.type_() {
1936                constants::BOOL | constants::FLOAT => {},
1937                _ => return Err(InvalidOperation),
1938            }
1939            let val = self.uniform_vec_section_float(val, src_offset, src_length, 1, location)?;
1940            self.send_command(WebGLCommand::Uniform1fv(location.id(), val));
1941            Ok(())
1942        });
1943    }
1944
1945    pub(crate) fn uniform2fv(
1946        &self,
1947        location: Option<&WebGLUniformLocation>,
1948        val: Float32ArrayOrUnrestrictedFloatSequence,
1949        src_offset: u32,
1950        src_length: u32,
1951    ) {
1952        self.with_location(location, |location| {
1953            match location.type_() {
1954                constants::BOOL_VEC2 | constants::FLOAT_VEC2 => {},
1955                _ => return Err(InvalidOperation),
1956            }
1957            let val = self.uniform_vec_section_float(val, src_offset, src_length, 2, location)?;
1958            self.send_command(WebGLCommand::Uniform2fv(location.id(), val));
1959            Ok(())
1960        });
1961    }
1962
1963    pub(crate) fn uniform2iv(
1964        &self,
1965        location: Option<&WebGLUniformLocation>,
1966        val: Int32ArrayOrLongSequence,
1967        src_offset: u32,
1968        src_length: u32,
1969    ) {
1970        self.with_location(location, |location| {
1971            match location.type_() {
1972                constants::BOOL_VEC2 | constants::INT_VEC2 => {},
1973                _ => return Err(InvalidOperation),
1974            }
1975            let val = self.uniform_vec_section_int(val, src_offset, src_length, 2, location)?;
1976            self.send_command(WebGLCommand::Uniform2iv(location.id(), val));
1977            Ok(())
1978        });
1979    }
1980
1981    pub(crate) fn uniform3fv(
1982        &self,
1983        location: Option<&WebGLUniformLocation>,
1984        val: Float32ArrayOrUnrestrictedFloatSequence,
1985        src_offset: u32,
1986        src_length: u32,
1987    ) {
1988        self.with_location(location, |location| {
1989            match location.type_() {
1990                constants::BOOL_VEC3 | constants::FLOAT_VEC3 => {},
1991                _ => return Err(InvalidOperation),
1992            }
1993            let val = self.uniform_vec_section_float(val, src_offset, src_length, 3, location)?;
1994            self.send_command(WebGLCommand::Uniform3fv(location.id(), val));
1995            Ok(())
1996        });
1997    }
1998
1999    pub(crate) fn uniform3iv(
2000        &self,
2001        location: Option<&WebGLUniformLocation>,
2002        val: Int32ArrayOrLongSequence,
2003        src_offset: u32,
2004        src_length: u32,
2005    ) {
2006        self.with_location(location, |location| {
2007            match location.type_() {
2008                constants::BOOL_VEC3 | constants::INT_VEC3 => {},
2009                _ => return Err(InvalidOperation),
2010            }
2011            let val = self.uniform_vec_section_int(val, src_offset, src_length, 3, location)?;
2012            self.send_command(WebGLCommand::Uniform3iv(location.id(), val));
2013            Ok(())
2014        });
2015    }
2016
2017    pub(crate) fn uniform4iv(
2018        &self,
2019        location: Option<&WebGLUniformLocation>,
2020        val: Int32ArrayOrLongSequence,
2021        src_offset: u32,
2022        src_length: u32,
2023    ) {
2024        self.with_location(location, |location| {
2025            match location.type_() {
2026                constants::BOOL_VEC4 | constants::INT_VEC4 => {},
2027                _ => return Err(InvalidOperation),
2028            }
2029            let val = self.uniform_vec_section_int(val, src_offset, src_length, 4, location)?;
2030            self.send_command(WebGLCommand::Uniform4iv(location.id(), val));
2031            Ok(())
2032        });
2033    }
2034
2035    pub(crate) fn uniform4fv(
2036        &self,
2037        location: Option<&WebGLUniformLocation>,
2038        val: Float32ArrayOrUnrestrictedFloatSequence,
2039        src_offset: u32,
2040        src_length: u32,
2041    ) {
2042        self.with_location(location, |location| {
2043            match location.type_() {
2044                constants::BOOL_VEC4 | constants::FLOAT_VEC4 => {},
2045                _ => return Err(InvalidOperation),
2046            }
2047            let val = self.uniform_vec_section_float(val, src_offset, src_length, 4, location)?;
2048            self.send_command(WebGLCommand::Uniform4fv(location.id(), val));
2049            Ok(())
2050        });
2051    }
2052
2053    pub(crate) fn uniform_matrix_2fv(
2054        &self,
2055        location: Option<&WebGLUniformLocation>,
2056        transpose: bool,
2057        val: Float32ArrayOrUnrestrictedFloatSequence,
2058        src_offset: u32,
2059        src_length: u32,
2060    ) {
2061        self.with_location(location, |location| {
2062            match location.type_() {
2063                constants::FLOAT_MAT2 => {},
2064                _ => return Err(InvalidOperation),
2065            }
2066            let val =
2067                self.uniform_matrix_section(val, src_offset, src_length, transpose, 4, location)?;
2068            self.send_command(WebGLCommand::UniformMatrix2fv(location.id(), val));
2069            Ok(())
2070        });
2071    }
2072
2073    pub(crate) fn uniform_matrix_3fv(
2074        &self,
2075        location: Option<&WebGLUniformLocation>,
2076        transpose: bool,
2077        val: Float32ArrayOrUnrestrictedFloatSequence,
2078        src_offset: u32,
2079        src_length: u32,
2080    ) {
2081        self.with_location(location, |location| {
2082            match location.type_() {
2083                constants::FLOAT_MAT3 => {},
2084                _ => return Err(InvalidOperation),
2085            }
2086            let val =
2087                self.uniform_matrix_section(val, src_offset, src_length, transpose, 9, location)?;
2088            self.send_command(WebGLCommand::UniformMatrix3fv(location.id(), val));
2089            Ok(())
2090        });
2091    }
2092
2093    pub(crate) fn uniform_matrix_4fv(
2094        &self,
2095        location: Option<&WebGLUniformLocation>,
2096        transpose: bool,
2097        val: Float32ArrayOrUnrestrictedFloatSequence,
2098        src_offset: u32,
2099        src_length: u32,
2100    ) {
2101        self.with_location(location, |location| {
2102            match location.type_() {
2103                constants::FLOAT_MAT4 => {},
2104                _ => return Err(InvalidOperation),
2105            }
2106            let val =
2107                self.uniform_matrix_section(val, src_offset, src_length, transpose, 16, location)?;
2108            self.send_command(WebGLCommand::UniformMatrix4fv(location.id(), val));
2109            Ok(())
2110        });
2111    }
2112
2113    pub(crate) fn get_buffer_param(
2114        &self,
2115        buffer: Option<DomRoot<WebGLBuffer>>,
2116        parameter: u32,
2117        mut retval: MutableHandleValue,
2118    ) {
2119        let Some(buffer) = buffer else {
2120            self.webgl_error(InvalidOperation);
2121            return retval.set(NullValue());
2122        };
2123
2124        retval.set(match parameter {
2125            constants::BUFFER_SIZE => Int32Value(buffer.capacity() as i32),
2126            constants::BUFFER_USAGE => Int32Value(buffer.usage() as i32),
2127            _ => {
2128                self.webgl_error(InvalidEnum);
2129                NullValue()
2130            },
2131        })
2132    }
2133}
2134
2135impl CanvasContext for WebGLRenderingContext {
2136    type ID = WebGLContextId;
2137
2138    fn context_id(&self) -> Self::ID {
2139        self.droppable.webgl_sender.context_id()
2140    }
2141
2142    fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
2143        Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
2144    }
2145
2146    fn resize(&self) {
2147        let size = self.size().cast();
2148        let (sender, receiver) = webgl_channel().unwrap();
2149        self.droppable
2150            .webgl_sender
2151            .send_resize(size, sender)
2152            .unwrap();
2153        // FIXME(#21718) The backend is allowed to choose a size smaller than
2154        // what was requested
2155        self.size.set(size);
2156        self.reflector_
2157            .update_memory_size(self, size.cast::<usize>().area() * 4);
2158
2159        if let Err(msg) = receiver.recv().unwrap() {
2160            error!("Error resizing WebGLContext: {}", msg);
2161            return;
2162        };
2163
2164        // ClearColor needs to be restored because after a resize the GLContext is recreated
2165        // and the framebuffer is cleared using the default black transparent color.
2166        let color = self.current_clear_color.get();
2167        self.send_command(WebGLCommand::ClearColor(color.0, color.1, color.2, color.3));
2168
2169        // WebGL Spec: Scissor rect must not change if the canvas is resized.
2170        // See: webgl/conformance-1.0.3/conformance/rendering/gl-scissor-canvas-dimensions.html
2171        // NativeContext handling library changes the scissor after a resize, so we need to reset the
2172        // default scissor when the canvas was created or the last scissor that the user set.
2173        let rect = self.current_scissor.get();
2174        self.send_command(WebGLCommand::Scissor(rect.0, rect.1, rect.2, rect.3));
2175
2176        // Bound texture must not change when the canvas is resized.
2177        // Right now surfman generates a new FBO and the bound texture is changed
2178        // in order to create a new render to texture attachment.
2179        // Send a command to re-bind the TEXTURE_2D, if any.
2180        if let Some(texture) = self
2181            .textures
2182            .active_texture_slot(constants::TEXTURE_2D, self.webgl_version())
2183            .unwrap()
2184            .get()
2185        {
2186            self.send_command(WebGLCommand::BindTexture(
2187                constants::TEXTURE_2D,
2188                Some(texture.id()),
2189            ));
2190        }
2191
2192        // Bound framebuffer must not change when the canvas is resized.
2193        // Right now surfman generates a new FBO on resize.
2194        // Send a command to re-bind the framebuffer, if any.
2195        if let Some(fbo) = self.bound_draw_framebuffer.get() {
2196            let id = WebGLFramebufferBindingRequest::Explicit(fbo.id());
2197            self.send_command(WebGLCommand::BindFramebuffer(constants::FRAMEBUFFER, id));
2198        }
2199    }
2200
2201    fn reset_bitmap(&self) {
2202        warn!("The WebGLRenderingContext 'reset_bitmap' is not implemented yet");
2203    }
2204
2205    // Used by HTMLCanvasElement.toDataURL
2206    //
2207    // This emits errors quite liberally, but the spec says that this operation
2208    // can fail and that it is UB what happens in that case.
2209    //
2210    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2>
2211    fn get_image_data(&self) -> Option<Snapshot> {
2212        if let Err(error) = self.validate_read_framebuffer() {
2213            self.webgl_error(error);
2214            return None;
2215        }
2216
2217        let mut size = self.size().cast();
2218
2219        let Some((fb_width, fb_height)) = self.get_current_framebuffer_size() else {
2220            self.webgl_error(InvalidOperation);
2221            return None;
2222        };
2223
2224        size.width = cmp::min(size.width, fb_width as u32);
2225        size.height = cmp::min(size.height, fb_height as u32);
2226
2227        let (sender, receiver) = generic_channel::channel().unwrap();
2228        self.send_command(WebGLCommand::ReadPixels(
2229            Rect::from_size(size),
2230            constants::RGBA,
2231            constants::UNSIGNED_BYTE,
2232            sender,
2233        ));
2234        let (data, alpha_mode) = receiver.recv().unwrap();
2235        Some(Snapshot::from_vec(
2236            size.cast(),
2237            SnapshotPixelFormat::RGBA,
2238            alpha_mode,
2239            data.to_vec(),
2240        ))
2241    }
2242
2243    fn mark_as_dirty(&self) {
2244        // If we have a bound framebuffer, then don't mark the canvas as dirty.
2245        if self.bound_draw_framebuffer.get().is_some() {
2246            return;
2247        }
2248
2249        // Dirtying the canvas is unnecessary if we're actively displaying immersive
2250        // XR content right now.
2251        if self.global().as_window().in_immersive_xr_session() {
2252            return;
2253        }
2254
2255        self.canvas.mark_as_dirty();
2256    }
2257}
2258
2259#[cfg(not(feature = "webgl_backtrace"))]
2260#[inline]
2261pub(crate) fn capture_webgl_backtrace() -> WebGLCommandBacktrace {
2262    WebGLCommandBacktrace {}
2263}
2264
2265#[cfg(feature = "webgl_backtrace")]
2266#[cfg_attr(feature = "webgl_backtrace", expect(unsafe_code))]
2267pub(crate) fn capture_webgl_backtrace() -> WebGLCommandBacktrace {
2268    let bt = Backtrace::new();
2269    unsafe {
2270        // TODO: https://github.com/servo/servo/issues/40600
2271        #[expect(unsafe_code)]
2272        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
2273        capture_stack!(&in(cx) let stack);
2274        WebGLCommandBacktrace {
2275            backtrace: format!("{:?}", bt),
2276            js_backtrace: stack.and_then(|s| s.as_string(None, js::jsapi::StackFormat::Default)),
2277        }
2278    }
2279}
2280
2281impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContext {
2282    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1>
2283    fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
2284        RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
2285    }
2286
2287    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11>
2288    fn Flush(&self) {
2289        self.send_command(WebGLCommand::Flush);
2290    }
2291
2292    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11>
2293    fn Finish(&self) {
2294        let (sender, receiver) = webgl_channel().unwrap();
2295        self.send_command(WebGLCommand::Finish(sender));
2296        receiver.recv().unwrap()
2297    }
2298
2299    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1>
2300    fn DrawingBufferWidth(&self) -> i32 {
2301        let (sender, receiver) = webgl_channel().unwrap();
2302        self.send_command(WebGLCommand::DrawingBufferWidth(sender));
2303        receiver.recv().unwrap()
2304    }
2305
2306    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1>
2307    fn DrawingBufferHeight(&self) -> i32 {
2308        let (sender, receiver) = webgl_channel().unwrap();
2309        self.send_command(WebGLCommand::DrawingBufferHeight(sender));
2310        receiver.recv().unwrap()
2311    }
2312
2313    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
2314    fn GetBufferParameter(
2315        &self,
2316        cx: &mut JSContext,
2317        target: u32,
2318        parameter: u32,
2319        mut retval: MutableHandleValue,
2320    ) {
2321        let buffer = match self.bound_buffer(cx, target) {
2322            Ok(buffer) => buffer,
2323            Err(error) => {
2324                self.webgl_error(error);
2325                return retval.set(NullValue());
2326            },
2327        };
2328        self.get_buffer_param(buffer, parameter, retval)
2329    }
2330
2331    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2332    fn GetParameter(&self, cx: &mut JSContext, parameter: u32, mut retval: MutableHandleValue) {
2333        if !self
2334            .extension_manager
2335            .is_get_parameter_name_enabled(parameter)
2336        {
2337            self.webgl_error(WebGLError::InvalidEnum);
2338            return retval.set(NullValue());
2339        }
2340
2341        match parameter {
2342            constants::ARRAY_BUFFER_BINDING => {
2343                self.bound_buffer_array.get().to_jsval(cx, retval);
2344                return;
2345            },
2346            constants::CURRENT_PROGRAM => {
2347                self.current_program.get().to_jsval(cx, retval);
2348                return;
2349            },
2350            constants::ELEMENT_ARRAY_BUFFER_BINDING => {
2351                let buffer = self.current_vao(cx).element_array_buffer().get();
2352                buffer.to_jsval(cx, retval);
2353                return;
2354            },
2355            constants::FRAMEBUFFER_BINDING => {
2356                self.bound_draw_framebuffer.get().to_jsval(cx, retval);
2357                return;
2358            },
2359            constants::RENDERBUFFER_BINDING => {
2360                self.bound_renderbuffer.get().to_jsval(cx, retval);
2361                return;
2362            },
2363            constants::TEXTURE_BINDING_2D => {
2364                let texture = self
2365                    .textures
2366                    .active_texture_slot(constants::TEXTURE_2D, self.webgl_version())
2367                    .unwrap()
2368                    .get();
2369                texture.to_jsval(cx, retval);
2370                return;
2371            },
2372            WebGL2RenderingContextConstants::TEXTURE_BINDING_2D_ARRAY => {
2373                let texture = self
2374                    .textures
2375                    .active_texture_slot(
2376                        WebGL2RenderingContextConstants::TEXTURE_2D_ARRAY,
2377                        self.webgl_version(),
2378                    )
2379                    .unwrap()
2380                    .get();
2381                texture.to_jsval(cx, retval);
2382                return;
2383            },
2384            WebGL2RenderingContextConstants::TEXTURE_BINDING_3D => {
2385                let texture = self
2386                    .textures
2387                    .active_texture_slot(
2388                        WebGL2RenderingContextConstants::TEXTURE_3D,
2389                        self.webgl_version(),
2390                    )
2391                    .unwrap()
2392                    .get();
2393                texture.to_jsval(cx, retval);
2394                return;
2395            },
2396            constants::TEXTURE_BINDING_CUBE_MAP => {
2397                let texture = self
2398                    .textures
2399                    .active_texture_slot(constants::TEXTURE_CUBE_MAP, self.webgl_version())
2400                    .unwrap()
2401                    .get();
2402                texture.to_jsval(cx, retval);
2403                return;
2404            },
2405            OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES => {
2406                let vao = self.current_vao.get().filter(|vao| vao.id().is_some());
2407                vao.to_jsval(cx, retval);
2408                return;
2409            },
2410            // In readPixels we currently support RGBA/UBYTE only.  If
2411            // we wanted to support other formats, we could ask the
2412            // driver, but we would need to check for
2413            // GL_OES_read_format support (assuming an underlying GLES
2414            // driver. Desktop is happy to format convert for us).
2415            constants::IMPLEMENTATION_COLOR_READ_FORMAT => {
2416                if self.validate_read_framebuffer().is_err() {
2417                    self.webgl_error(InvalidOperation);
2418                    return retval.set(NullValue());
2419                }
2420                return retval.set(Int32Value(constants::RGBA as i32));
2421            },
2422            constants::IMPLEMENTATION_COLOR_READ_TYPE => {
2423                if self.validate_read_framebuffer().is_err() {
2424                    self.webgl_error(InvalidOperation);
2425                    return retval.set(NullValue());
2426                }
2427                return retval.set(Int32Value(constants::UNSIGNED_BYTE as i32));
2428            },
2429            constants::COMPRESSED_TEXTURE_FORMATS => {
2430                let format_ids = self.extension_manager.get_tex_compression_ids();
2431
2432                rooted!(&in(cx) let mut rval = ptr::null_mut::<JSObject>());
2433                create_buffer_source::<Uint32>(cx, &format_ids, rval.handle_mut()).unwrap();
2434                return retval.set(ObjectValue(rval.get()));
2435            },
2436            constants::VERSION => {
2437                "WebGL 1.0".to_jsval(cx, retval);
2438                return;
2439            },
2440            constants::RENDERER | constants::VENDOR => {
2441                "Mozilla/Servo".to_jsval(cx, retval);
2442                return;
2443            },
2444            WEBGLDebugRendererInfoConstants::UNMASKED_VENDOR_WEBGL |
2445            WEBGLDebugRendererInfoConstants::UNMASKED_RENDERER_WEBGL => {
2446                let param = if parameter == WEBGLDebugRendererInfoConstants::UNMASKED_VENDOR_WEBGL {
2447                    ParameterString::UnmaskedVendor
2448                } else {
2449                    ParameterString::UnmaskedRenderer
2450                };
2451                let (sender, receiver) = webgl_channel().unwrap();
2452                self.send_command(WebGLCommand::GetParameterString(param, sender));
2453                receiver.recv().unwrap().to_jsval(cx, retval);
2454                return;
2455            },
2456            constants::SHADING_LANGUAGE_VERSION => {
2457                "WebGL GLSL ES 1.0".to_jsval(cx, retval);
2458                return;
2459            },
2460            constants::UNPACK_FLIP_Y_WEBGL => {
2461                let unpack = self.texture_unpacking_settings.get();
2462                retval.set(BooleanValue(unpack.contains(TextureUnpacking::FLIP_Y_AXIS)));
2463                return;
2464            },
2465            constants::UNPACK_PREMULTIPLY_ALPHA_WEBGL => {
2466                let unpack = self.texture_unpacking_settings.get();
2467                retval.set(BooleanValue(
2468                    unpack.contains(TextureUnpacking::PREMULTIPLY_ALPHA),
2469                ));
2470                return;
2471            },
2472            constants::PACK_ALIGNMENT => {
2473                retval.set(UInt32Value(self.texture_packing_alignment.get() as u32));
2474                return;
2475            },
2476            constants::UNPACK_ALIGNMENT => {
2477                retval.set(UInt32Value(self.texture_unpacking_alignment.get()));
2478                return;
2479            },
2480            constants::UNPACK_COLORSPACE_CONVERSION_WEBGL => {
2481                let unpack = self.texture_unpacking_settings.get();
2482                retval.set(UInt32Value(
2483                    if unpack.contains(TextureUnpacking::CONVERT_COLORSPACE) {
2484                        constants::BROWSER_DEFAULT_WEBGL
2485                    } else {
2486                        constants::NONE
2487                    },
2488                ));
2489                return;
2490            },
2491            _ => {},
2492        }
2493
2494        // Handle any MAX_ parameters by retrieving the limits that were stored
2495        // when this context was created.
2496        let limit = match parameter {
2497            constants::MAX_VERTEX_ATTRIBS => Some(self.limits.max_vertex_attribs),
2498            constants::MAX_TEXTURE_SIZE => Some(self.limits.max_tex_size),
2499            constants::MAX_CUBE_MAP_TEXTURE_SIZE => Some(self.limits.max_cube_map_tex_size),
2500            constants::MAX_COMBINED_TEXTURE_IMAGE_UNITS => {
2501                Some(self.limits.max_combined_texture_image_units)
2502            },
2503            constants::MAX_FRAGMENT_UNIFORM_VECTORS => {
2504                Some(self.limits.max_fragment_uniform_vectors)
2505            },
2506            constants::MAX_RENDERBUFFER_SIZE => Some(self.limits.max_renderbuffer_size),
2507            constants::MAX_TEXTURE_IMAGE_UNITS => Some(self.limits.max_texture_image_units),
2508            constants::MAX_VARYING_VECTORS => Some(self.limits.max_varying_vectors),
2509            constants::MAX_VERTEX_TEXTURE_IMAGE_UNITS => {
2510                Some(self.limits.max_vertex_texture_image_units)
2511            },
2512            constants::MAX_VERTEX_UNIFORM_VECTORS => Some(self.limits.max_vertex_uniform_vectors),
2513            _ => None,
2514        };
2515        if let Some(limit) = limit {
2516            retval.set(UInt32Value(limit));
2517            return;
2518        }
2519
2520        if let Ok(value) = self.capabilities.is_enabled(parameter) {
2521            retval.set(BooleanValue(value));
2522            return;
2523        }
2524
2525        let param = match Parameter::from_u32(parameter) {
2526            Ok(param) => param,
2527            Err(error) => {
2528                self.webgl_error(error);
2529                return retval.set(NullValue());
2530            },
2531        };
2532
2533        match param {
2534            Parameter::Bool(param) => {
2535                let (sender, receiver) = webgl_channel().unwrap();
2536                self.send_command(WebGLCommand::GetParameterBool(param, sender));
2537                retval.set(BooleanValue(receiver.recv().unwrap()))
2538            },
2539            Parameter::Bool4(param) => {
2540                let (sender, receiver) = webgl_channel().unwrap();
2541                self.send_command(WebGLCommand::GetParameterBool4(param, sender));
2542                receiver.recv().unwrap().to_jsval(cx, retval);
2543            },
2544            Parameter::Int(param) => {
2545                let (sender, receiver) = webgl_channel().unwrap();
2546                self.send_command(WebGLCommand::GetParameterInt(param, sender));
2547                retval.set(Int32Value(receiver.recv().unwrap()))
2548            },
2549            Parameter::UInt(param) => {
2550                let (sender, receiver) = webgl_channel().unwrap();
2551                self.send_command(WebGLCommand::GetParameterUInt(param, sender));
2552                retval.set(UInt32Value(receiver.recv().unwrap()))
2553            },
2554            Parameter::Int2(param) => {
2555                let (sender, receiver) = webgl_channel().unwrap();
2556                self.send_command(WebGLCommand::GetParameterInt2(param, sender));
2557                rooted!(&in(cx) let mut rval = ptr::null_mut::<JSObject>());
2558                create_buffer_source::<Int32>(cx, &receiver.recv().unwrap(), rval.handle_mut())
2559                    .unwrap();
2560                retval.set(ObjectValue(rval.get()))
2561            },
2562            Parameter::Int4(param) => {
2563                let (sender, receiver) = webgl_channel().unwrap();
2564                self.send_command(WebGLCommand::GetParameterInt4(param, sender));
2565                rooted!(&in(cx) let mut rval = ptr::null_mut::<JSObject>());
2566                create_buffer_source::<Int32>(cx, &receiver.recv().unwrap(), rval.handle_mut())
2567                    .unwrap();
2568                retval.set(ObjectValue(rval.get()))
2569            },
2570            Parameter::Float(param) => {
2571                let (sender, receiver) = webgl_channel().unwrap();
2572                self.send_command(WebGLCommand::GetParameterFloat(param, sender));
2573                retval.set(DoubleValue(receiver.recv().unwrap() as f64))
2574            },
2575            Parameter::Float2(param) => {
2576                let (sender, receiver) = webgl_channel().unwrap();
2577                self.send_command(WebGLCommand::GetParameterFloat2(param, sender));
2578                rooted!(&in(cx) let mut rval = ptr::null_mut::<JSObject>());
2579                create_buffer_source::<Float32>(cx, &receiver.recv().unwrap(), rval.handle_mut())
2580                    .unwrap();
2581                retval.set(ObjectValue(rval.get()))
2582            },
2583            Parameter::Float4(param) => {
2584                let (sender, receiver) = webgl_channel().unwrap();
2585                self.send_command(WebGLCommand::GetParameterFloat4(param, sender));
2586                rooted!(&in(cx) let mut rval = ptr::null_mut::<JSObject>());
2587                create_buffer_source::<Float32>(cx, &receiver.recv().unwrap(), rval.handle_mut())
2588                    .unwrap();
2589                retval.set(ObjectValue(rval.get()))
2590            },
2591        }
2592    }
2593
2594    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
2595    fn GetTexParameter(
2596        &self,
2597        _cx: &mut JSContext,
2598        target: u32,
2599        pname: u32,
2600        mut retval: MutableHandleValue,
2601    ) {
2602        let texture_slot = match self
2603            .textures
2604            .active_texture_slot(target, self.webgl_version())
2605        {
2606            Ok(texture_slot) => texture_slot,
2607            Err(error) => {
2608                self.webgl_error(error);
2609                return retval.set(NullValue());
2610            },
2611        };
2612        let Some(texture) = texture_slot.get() else {
2613            self.webgl_error(InvalidOperation);
2614            return retval.set(NullValue());
2615        };
2616
2617        if !self
2618            .extension_manager
2619            .is_get_tex_parameter_name_enabled(pname)
2620        {
2621            self.webgl_error(InvalidEnum);
2622            return retval.set(NullValue());
2623        }
2624
2625        match pname {
2626            constants::TEXTURE_MAG_FILTER => return retval.set(UInt32Value(texture.mag_filter())),
2627            constants::TEXTURE_MIN_FILTER => return retval.set(UInt32Value(texture.min_filter())),
2628            _ => {},
2629        }
2630
2631        let texparam = match TexParameter::from_u32(pname) {
2632            Ok(texparam) => texparam,
2633            Err(error) => {
2634                self.webgl_error(error);
2635                return retval.set(NullValue());
2636            },
2637        };
2638        if self.webgl_version() < texparam.required_webgl_version() {
2639            self.webgl_error(InvalidEnum);
2640            return retval.set(NullValue());
2641        }
2642
2643        if let Some(value) = texture.maybe_get_tex_parameter(texparam) {
2644            match value {
2645                TexParameterValue::Float(v) => retval.set(DoubleValue(v as f64)),
2646                TexParameterValue::Int(v) => retval.set(Int32Value(v)),
2647                TexParameterValue::Bool(v) => retval.set(BooleanValue(v)),
2648            }
2649            return;
2650        }
2651
2652        match texparam {
2653            TexParameter::Float(param) => {
2654                let (sender, receiver) = webgl_channel().unwrap();
2655                self.send_command(WebGLCommand::GetTexParameterFloat(target, param, sender));
2656                retval.set(DoubleValue(receiver.recv().unwrap() as f64))
2657            },
2658            TexParameter::Int(param) => {
2659                let (sender, receiver) = webgl_channel().unwrap();
2660                self.send_command(WebGLCommand::GetTexParameterInt(target, param, sender));
2661                retval.set(Int32Value(receiver.recv().unwrap()))
2662            },
2663            TexParameter::Bool(param) => {
2664                let (sender, receiver) = webgl_channel().unwrap();
2665                self.send_command(WebGLCommand::GetTexParameterBool(target, param, sender));
2666                retval.set(BooleanValue(receiver.recv().unwrap()))
2667            },
2668        }
2669    }
2670
2671    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2672    fn GetError(&self) -> u32 {
2673        let error_code = if let Some(error) = self.last_error.get() {
2674            match error {
2675                WebGLError::InvalidEnum => constants::INVALID_ENUM,
2676                WebGLError::InvalidFramebufferOperation => constants::INVALID_FRAMEBUFFER_OPERATION,
2677                WebGLError::InvalidValue => constants::INVALID_VALUE,
2678                WebGLError::InvalidOperation => constants::INVALID_OPERATION,
2679                WebGLError::OutOfMemory => constants::OUT_OF_MEMORY,
2680                WebGLError::ContextLost => constants::CONTEXT_LOST_WEBGL,
2681            }
2682        } else {
2683            constants::NO_ERROR
2684        };
2685        self.last_error.set(None);
2686        error_code
2687    }
2688
2689    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.2>
2690    fn GetContextAttributes(&self) -> Option<WebGLContextAttributes> {
2691        let (sender, receiver) = webgl_channel().unwrap();
2692
2693        // If the send does not succeed, assume context lost
2694        let backtrace = capture_webgl_backtrace();
2695        if self
2696            .droppable
2697            .webgl_sender
2698            .send(WebGLCommand::GetContextAttributes(sender), backtrace)
2699            .is_err()
2700        {
2701            return None;
2702        }
2703
2704        let attrs = receiver.recv().unwrap();
2705
2706        Some(WebGLContextAttributes {
2707            alpha: attrs.alpha,
2708            antialias: attrs.antialias,
2709            depth: attrs.depth,
2710            failIfMajorPerformanceCaveat: false,
2711            preferLowPowerToHighPerformance: false,
2712            premultipliedAlpha: attrs.premultiplied_alpha,
2713            preserveDrawingBuffer: attrs.preserve_drawing_buffer,
2714            stencil: attrs.stencil,
2715        })
2716    }
2717
2718    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.13>
2719    fn IsContextLost(&self) -> bool {
2720        false
2721    }
2722
2723    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14>
2724    fn GetSupportedExtensions(&self) -> Option<Vec<DOMString>> {
2725        self.extension_manager
2726            .init_once(|| self.get_gl_extensions());
2727        let extensions = self.extension_manager.get_supported_extensions();
2728        Some(
2729            extensions
2730                .iter()
2731                .map(|name| DOMString::from(*name))
2732                .collect(),
2733        )
2734    }
2735
2736    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14>
2737    fn GetExtension(
2738        &self,
2739        cx: &mut js::context::JSContext,
2740        name: DOMString,
2741        rval: MutableHandleObject,
2742    ) {
2743        self.extension_manager
2744            .init_once(|| self.get_gl_extensions());
2745        self.extension_manager
2746            .get_or_init_extension(cx, &name, self, rval);
2747    }
2748
2749    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2750    fn ActiveTexture(&self, texture: u32) {
2751        if let Err(error) = self.textures.set_active_unit_enum(texture) {
2752            return self.webgl_error(error);
2753        }
2754        self.send_command(WebGLCommand::ActiveTexture(texture));
2755    }
2756
2757    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2758    fn BlendColor(&self, r: f32, g: f32, b: f32, a: f32) {
2759        self.send_command(WebGLCommand::BlendColor(r, g, b, a));
2760    }
2761
2762    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2763    fn BlendEquation(&self, mode: u32) {
2764        if let Err(error) = self.validate_blend_mode(mode) {
2765            return self.webgl_error(error);
2766        }
2767        self.send_command(WebGLCommand::BlendEquation(mode))
2768    }
2769
2770    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2771    fn BlendEquationSeparate(&self, mode_rgb: u32, mode_alpha: u32) {
2772        if let Err(error) = self.validate_blend_mode(mode_rgb) {
2773            return self.webgl_error(error);
2774        }
2775        if let Err(error) = self.validate_blend_mode(mode_alpha) {
2776            return self.webgl_error(error);
2777        }
2778        self.send_command(WebGLCommand::BlendEquationSeparate(mode_rgb, mode_alpha));
2779    }
2780
2781    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2782    fn BlendFunc(&self, src_factor: u32, dest_factor: u32) {
2783        // From the WebGL 1.0 spec, 6.13: Viewport Depth Range:
2784        //
2785        //     A call to blendFunc will generate an INVALID_OPERATION error if one of the two
2786        //     factors is set to CONSTANT_COLOR or ONE_MINUS_CONSTANT_COLOR and the other to
2787        //     CONSTANT_ALPHA or ONE_MINUS_CONSTANT_ALPHA.
2788        if has_invalid_blend_constants(src_factor, dest_factor) {
2789            return self.webgl_error(InvalidOperation);
2790        }
2791        if has_invalid_blend_constants(dest_factor, src_factor) {
2792            return self.webgl_error(InvalidOperation);
2793        }
2794
2795        self.send_command(WebGLCommand::BlendFunc(src_factor, dest_factor));
2796    }
2797
2798    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
2799    fn BlendFuncSeparate(&self, src_rgb: u32, dest_rgb: u32, src_alpha: u32, dest_alpha: u32) {
2800        // From the WebGL 1.0 spec, 6.13: Viewport Depth Range:
2801        //
2802        //     A call to blendFuncSeparate will generate an INVALID_OPERATION error if srcRGB is
2803        //     set to CONSTANT_COLOR or ONE_MINUS_CONSTANT_COLOR and dstRGB is set to
2804        //     CONSTANT_ALPHA or ONE_MINUS_CONSTANT_ALPHA or vice versa.
2805        if has_invalid_blend_constants(src_rgb, dest_rgb) {
2806            return self.webgl_error(InvalidOperation);
2807        }
2808        if has_invalid_blend_constants(dest_rgb, src_rgb) {
2809            return self.webgl_error(InvalidOperation);
2810        }
2811
2812        self.send_command(WebGLCommand::BlendFuncSeparate(
2813            src_rgb, dest_rgb, src_alpha, dest_alpha,
2814        ));
2815    }
2816
2817    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
2818    fn AttachShader(&self, program: &WebGLProgram, shader: &WebGLShader) {
2819        if let Err(error) = self.validate_ownership(program) {
2820            return self.webgl_error(error);
2821        }
2822        if let Err(error) = self.validate_ownership(shader) {
2823            return self.webgl_error(error);
2824        }
2825        if let Err(error) = program.attach_shader(shader) {
2826            self.webgl_error(error);
2827        }
2828    }
2829
2830    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
2831    fn DetachShader(&self, program: &WebGLProgram, shader: &WebGLShader) {
2832        if let Err(error) = self.validate_ownership(program) {
2833            return self.webgl_error(error);
2834        }
2835        if let Err(error) = self.validate_ownership(shader) {
2836            return self.webgl_error(error);
2837        }
2838        if let Err(error) = program.detach_shader(shader) {
2839            self.webgl_error(error);
2840        }
2841    }
2842
2843    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
2844    fn BindAttribLocation(&self, program: &WebGLProgram, index: u32, name: DOMString) {
2845        if let Err(error) = self.validate_ownership(program) {
2846            return self.webgl_error(error);
2847        }
2848        if let Err(error) = program.bind_attrib_location(index, name) {
2849            self.webgl_error(error);
2850        }
2851    }
2852
2853    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
2854    fn BindBuffer(&self, cx: &mut JSContext, target: u32, buffer: Option<&WebGLBuffer>) {
2855        let current_vao;
2856        let slot = match target {
2857            constants::ARRAY_BUFFER => &self.bound_buffer_array,
2858            constants::ELEMENT_ARRAY_BUFFER => {
2859                current_vao = self.current_vao(cx);
2860                current_vao.element_array_buffer()
2861            },
2862            _ => return self.webgl_error(InvalidEnum),
2863        };
2864        self.bind_buffer_maybe(slot, target, buffer);
2865    }
2866
2867    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
2868    fn BindFramebuffer(&self, target: u32, framebuffer: Option<&WebGLFramebuffer>) {
2869        if let Err(error) = self.validate_new_framebuffer_binding(framebuffer) {
2870            return self.webgl_error(error);
2871        }
2872
2873        if target != constants::FRAMEBUFFER {
2874            return self.webgl_error(InvalidEnum);
2875        }
2876
2877        self.bind_framebuffer_to(target, framebuffer, &self.bound_draw_framebuffer)
2878    }
2879
2880    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
2881    fn BindRenderbuffer(&self, target: u32, renderbuffer: Option<&WebGLRenderbuffer>) {
2882        if let Some(rb) = renderbuffer &&
2883            let Err(error) = self.validate_ownership(rb)
2884        {
2885            return self.webgl_error(error);
2886        }
2887
2888        if target != constants::RENDERBUFFER {
2889            return self.webgl_error(InvalidEnum);
2890        }
2891
2892        match renderbuffer {
2893            // Implementations differ on what to do in the deleted
2894            // case: Chromium currently unbinds, and Gecko silently
2895            // returns.  The conformance tests don't cover this case.
2896            Some(renderbuffer) if !renderbuffer.is_deleted() => {
2897                self.bound_renderbuffer.set(Some(renderbuffer));
2898                renderbuffer.bind(target);
2899            },
2900            _ => {
2901                if renderbuffer.is_some() {
2902                    self.webgl_error(InvalidOperation);
2903                }
2904
2905                self.bound_renderbuffer.set(None);
2906                // Unbind the currently bound renderbuffer
2907                self.send_command(WebGLCommand::BindRenderbuffer(target, None));
2908            },
2909        }
2910    }
2911
2912    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
2913    fn BindTexture(&self, target: u32, texture: Option<&WebGLTexture>) {
2914        if let Some(texture) = texture &&
2915            let Err(error) = self.validate_ownership(texture)
2916        {
2917            return self.webgl_error(error);
2918        }
2919
2920        let texture_slot = match self
2921            .textures
2922            .active_texture_slot(target, self.webgl_version())
2923        {
2924            Ok(texture_slot) => texture_slot,
2925            Err(error) => return self.webgl_error(error),
2926        };
2927
2928        if let Some(texture) = texture {
2929            if let Err(error) = texture.bind(target) {
2930                return self.webgl_error(error);
2931            }
2932        } else {
2933            self.send_command(WebGLCommand::BindTexture(target, None));
2934        }
2935        texture_slot.set(texture);
2936    }
2937
2938    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
2939    fn GenerateMipmap(&self, target: u32) {
2940        let texture_slot = match self
2941            .textures
2942            .active_texture_slot(target, self.webgl_version())
2943        {
2944            Ok(texture_slot) => texture_slot,
2945            Err(error) => return self.webgl_error(error),
2946        };
2947        let Some(texture) = texture_slot.get() else {
2948            return self.webgl_error(InvalidOperation);
2949        };
2950        if let Err(error) = texture.generate_mipmap() {
2951            self.webgl_error(error);
2952        }
2953    }
2954
2955    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
2956    fn BufferData_(
2957        &self,
2958        cx: &mut JSContext,
2959        target: u32,
2960        data: Option<ArrayBufferViewOrArrayBuffer>,
2961        usage: u32,
2962    ) {
2963        let usage = match self.buffer_usage(usage) {
2964            Ok(usage) => usage,
2965            Err(error) => return self.webgl_error(error),
2966        };
2967        let bound_buffer = match self.bound_buffer(cx, target) {
2968            Ok(bound_buffer) => bound_buffer,
2969            Err(error) => return self.webgl_error(error),
2970        };
2971        self.buffer_data(cx.no_gc(), target, data, usage, bound_buffer)
2972    }
2973
2974    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
2975    fn BufferData(&self, cx: &mut JSContext, target: u32, size: i64, usage: u32) {
2976        let usage = match self.buffer_usage(usage) {
2977            Ok(usage) => usage,
2978            Err(error) => return self.webgl_error(error),
2979        };
2980        let bound_buffer = match self.bound_buffer(cx, target) {
2981            Ok(bound_buffer) => bound_buffer,
2982            Err(error) => return self.webgl_error(error),
2983        };
2984        self.buffer_data_(target, size, usage, bound_buffer)
2985    }
2986
2987    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
2988    fn BufferSubData(
2989        &self,
2990        cx: &mut JSContext,
2991        target: u32,
2992        offset: i64,
2993        data: ArrayBufferViewOrArrayBuffer,
2994    ) {
2995        let bound_buffer = match self.bound_buffer(cx, target) {
2996            Ok(bound_buffer) => bound_buffer,
2997            Err(error) => return self.webgl_error(error),
2998        };
2999        self.buffer_sub_data(cx.no_gc(), target, offset, data, bound_buffer)
3000    }
3001
3002    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
3003    fn CompressedTexImage2D(
3004        &self,
3005        no_gc: &NoGC,
3006        target: u32,
3007        level: i32,
3008        internal_format: u32,
3009        width: i32,
3010        height: i32,
3011        border: i32,
3012        data: CustomAutoRooterGuard<ArrayBufferView>,
3013    ) {
3014        let data = data.as_slice_safe(no_gc).unwrap_or(&[]);
3015        self.compressed_tex_image_2d(target, level, internal_format, width, height, border, data)
3016    }
3017
3018    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
3019    fn CompressedTexSubImage2D(
3020        &self,
3021        no_gc: &NoGC,
3022        target: u32,
3023        level: i32,
3024        xoffset: i32,
3025        yoffset: i32,
3026        width: i32,
3027        height: i32,
3028        format: u32,
3029        data: CustomAutoRooterGuard<ArrayBufferView>,
3030    ) {
3031        let data = data.as_slice_safe(no_gc).unwrap_or(&[]);
3032        self.compressed_tex_sub_image_2d(
3033            target, level, xoffset, yoffset, width, height, format, data,
3034        )
3035    }
3036
3037    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
3038    fn CopyTexImage2D(
3039        &self,
3040        target: u32,
3041        level: i32,
3042        internal_format: u32,
3043        x: i32,
3044        y: i32,
3045        width: i32,
3046        height: i32,
3047        border: i32,
3048    ) {
3049        if let Err(error) = self.validate_read_framebuffer() {
3050            return self.webgl_error(error);
3051        }
3052
3053        let validator = CommonTexImage2DValidator::new(
3054            self,
3055            target,
3056            level,
3057            internal_format,
3058            width,
3059            height,
3060            border,
3061        );
3062        let CommonTexImage2DValidatorResult {
3063            texture,
3064            target,
3065            level,
3066            internal_format,
3067            width,
3068            height,
3069            border,
3070        } = match validator.validate() {
3071            Ok(result) => result,
3072            Err(_) => return,
3073        };
3074
3075        if texture.is_immutable() {
3076            return self.webgl_error(InvalidOperation);
3077        }
3078
3079        let source_format = match self.bound_draw_framebuffer.get() {
3080            Some(fb) => fb.attachment_effective_format(fb.read_buffer()),
3081            None => {
3082                let attrs = self.GetContextAttributes().unwrap();
3083                Some(if attrs.alpha {
3084                    TexFormat::RGBA8
3085                } else {
3086                    TexFormat::RGB8
3087                })
3088            },
3089        };
3090        let Some(source_format) = source_format else {
3091            return self.webgl_error(InvalidOperation);
3092        };
3093        let data_type = match validate_copy_tex_image_formats(source_format, internal_format) {
3094            Ok(data_type) => data_type,
3095            Err(error) => return self.webgl_error(error),
3096        };
3097
3098        // NB: TexImage2D depth is always equal to 1
3099        if let Err(error) =
3100            texture.initialize(target, width, height, 1, internal_format, level, data_type)
3101        {
3102            self.webgl_error(error);
3103        }
3104
3105        let msg = WebGLCommand::CopyTexImage2D(
3106            target.as_gl_constant(),
3107            level as i32,
3108            internal_format.as_gl_constant(),
3109            x,
3110            y,
3111            width as i32,
3112            height as i32,
3113            border as i32,
3114        );
3115
3116        self.send_command(msg);
3117
3118        if let Some(framebuffer) = self.bound_draw_framebuffer.get() {
3119            framebuffer.invalidate_texture(&texture);
3120        }
3121    }
3122
3123    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
3124    fn CopyTexSubImage2D(
3125        &self,
3126        target: u32,
3127        level: i32,
3128        xoffset: i32,
3129        yoffset: i32,
3130        x: i32,
3131        y: i32,
3132        width: i32,
3133        height: i32,
3134    ) {
3135        if let Err(error) = self.validate_read_framebuffer() {
3136            return self.webgl_error(error);
3137        }
3138
3139        // NB: We use a dummy (valid) format and border in order to reuse the
3140        // common validations, but this should have its own validator.
3141        let validator = CommonTexImage2DValidator::new(
3142            self,
3143            target,
3144            level,
3145            TexFormat::RGBA.as_gl_constant(),
3146            width,
3147            height,
3148            0,
3149        );
3150        let CommonTexImage2DValidatorResult {
3151            texture,
3152            target,
3153            level,
3154            width,
3155            height,
3156            ..
3157        } = match validator.validate() {
3158            Ok(result) => result,
3159            Err(_) => return,
3160        };
3161
3162        let image_info = match texture.image_info_for_target(&target, level) {
3163            Some(info) => info,
3164            None => return self.webgl_error(InvalidOperation),
3165        };
3166
3167        // GL_INVALID_VALUE is generated if:
3168        //   - xoffset or yoffset is less than 0
3169        //   - x offset plus the width is greater than the texture width
3170        //   - y offset plus the height is greater than the texture height
3171        if xoffset < 0 ||
3172            (xoffset as u32 + width) > image_info.width() ||
3173            yoffset < 0 ||
3174            (yoffset as u32 + height) > image_info.height()
3175        {
3176            self.webgl_error(InvalidValue);
3177            return;
3178        }
3179
3180        let msg = WebGLCommand::CopyTexSubImage2D(
3181            target.as_gl_constant(),
3182            level as i32,
3183            xoffset,
3184            yoffset,
3185            x,
3186            y,
3187            width as i32,
3188            height as i32,
3189        );
3190
3191        self.send_command(msg);
3192    }
3193
3194    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11>
3195    fn Clear(&self, mask: u32) {
3196        if let Err(error) = self.validate_framebuffer() {
3197            return self.webgl_error(error);
3198        }
3199        if mask &
3200            !(constants::DEPTH_BUFFER_BIT |
3201                constants::STENCIL_BUFFER_BIT |
3202                constants::COLOR_BUFFER_BIT) !=
3203            0
3204        {
3205            return self.webgl_error(InvalidValue);
3206        }
3207
3208        self.send_command(WebGLCommand::Clear(mask));
3209        self.mark_as_dirty();
3210    }
3211
3212    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3213    fn ClearColor(&self, red: f32, green: f32, blue: f32, alpha: f32) {
3214        self.current_clear_color.set((red, green, blue, alpha));
3215        self.send_command(WebGLCommand::ClearColor(red, green, blue, alpha));
3216    }
3217
3218    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3219    fn ClearDepth(&self, depth: f32) {
3220        self.send_command(WebGLCommand::ClearDepth(depth))
3221    }
3222
3223    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3224    fn ClearStencil(&self, stencil: i32) {
3225        self.send_command(WebGLCommand::ClearStencil(stencil))
3226    }
3227
3228    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3229    fn ColorMask(&self, r: bool, g: bool, b: bool, a: bool) {
3230        self.send_command(WebGLCommand::ColorMask(r, g, b, a))
3231    }
3232
3233    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3234    fn CullFace(&self, mode: u32) {
3235        match mode {
3236            constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => {
3237                self.send_command(WebGLCommand::CullFace(mode))
3238            },
3239            _ => self.webgl_error(InvalidEnum),
3240        }
3241    }
3242
3243    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3244    fn FrontFace(&self, mode: u32) {
3245        match mode {
3246            constants::CW | constants::CCW => self.send_command(WebGLCommand::FrontFace(mode)),
3247            _ => self.webgl_error(InvalidEnum),
3248        }
3249    }
3250    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3251    fn DepthFunc(&self, func: u32) {
3252        match func {
3253            constants::NEVER |
3254            constants::LESS |
3255            constants::EQUAL |
3256            constants::LEQUAL |
3257            constants::GREATER |
3258            constants::NOTEQUAL |
3259            constants::GEQUAL |
3260            constants::ALWAYS => self.send_command(WebGLCommand::DepthFunc(func)),
3261            _ => self.webgl_error(InvalidEnum),
3262        }
3263    }
3264
3265    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3266    fn DepthMask(&self, flag: bool) {
3267        self.send_command(WebGLCommand::DepthMask(flag))
3268    }
3269
3270    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3271    fn DepthRange(&self, near: f32, far: f32) {
3272        // https://www.khronos.org/registry/webgl/specs/latest/1.0/#VIEWPORT_DEPTH_RANGE
3273        if near > far {
3274            return self.webgl_error(InvalidOperation);
3275        }
3276        self.send_command(WebGLCommand::DepthRange(near, far))
3277    }
3278
3279    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3280    fn Enable(&self, cap: u32) {
3281        let changed = match self.capabilities.set(cap, true) {
3282            Ok(changed) => changed,
3283            Err(error) => return self.webgl_error(error),
3284        };
3285        if changed {
3286            self.send_command(WebGLCommand::Enable(cap));
3287        }
3288    }
3289
3290    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
3291    fn Disable(&self, cap: u32) {
3292        let changed = match self.capabilities.set(cap, false) {
3293            Ok(changed) => changed,
3294            Err(error) => return self.webgl_error(error),
3295        };
3296        if changed {
3297            self.send_command(WebGLCommand::Disable(cap));
3298        }
3299    }
3300
3301    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3302    fn CompileShader(&self, shader: &WebGLShader) {
3303        if let Err(error) = self.validate_ownership(shader) {
3304            return self.webgl_error(error);
3305        }
3306        if let Err(error) = shader.compile(
3307            self.api_type,
3308            self.webgl_version,
3309            self.glsl_version,
3310            &self.limits,
3311            &self.extension_manager,
3312        ) {
3313            self.webgl_error(error);
3314        }
3315    }
3316
3317    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
3318    fn CreateBuffer(&self, cx: &mut JSContext) -> Option<DomRoot<WebGLBuffer>> {
3319        WebGLBuffer::maybe_new(cx, self)
3320    }
3321
3322    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
3323    fn CreateFramebuffer(&self, cx: &mut JSContext) -> Option<DomRoot<WebGLFramebuffer>> {
3324        WebGLFramebuffer::maybe_new(cx, self)
3325    }
3326
3327    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
3328    fn CreateRenderbuffer(&self, cx: &mut JSContext) -> Option<DomRoot<WebGLRenderbuffer>> {
3329        WebGLRenderbuffer::maybe_new(cx, self)
3330    }
3331
3332    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
3333    fn CreateTexture(&self, cx: &mut JSContext) -> Option<DomRoot<WebGLTexture>> {
3334        WebGLTexture::maybe_new(cx, self)
3335    }
3336
3337    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3338    fn CreateProgram(&self, cx: &mut JSContext) -> Option<DomRoot<WebGLProgram>> {
3339        WebGLProgram::maybe_new(cx, self)
3340    }
3341
3342    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3343    fn CreateShader(&self, cx: &mut JSContext, shader_type: u32) -> Option<DomRoot<WebGLShader>> {
3344        match shader_type {
3345            constants::VERTEX_SHADER | constants::FRAGMENT_SHADER => {},
3346            _ => {
3347                self.webgl_error(InvalidEnum);
3348                return None;
3349            },
3350        }
3351        WebGLShader::maybe_new(cx, self, shader_type)
3352    }
3353
3354    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
3355    fn DeleteBuffer(&self, cx: &mut JSContext, buffer: Option<&WebGLBuffer>) {
3356        let buffer = match buffer {
3357            Some(buffer) => buffer,
3358            None => return,
3359        };
3360        if let Err(error) = self.validate_ownership(buffer) {
3361            return self.webgl_error(error);
3362        }
3363        if buffer.is_marked_for_deletion() {
3364            return;
3365        }
3366        self.current_vao(cx).unbind_buffer(buffer);
3367        if self.bound_buffer_array.get().is_some_and(|b| buffer == &*b) {
3368            self.bound_buffer_array.set(None);
3369            buffer.decrement_attached_counter(Operation::Infallible);
3370        }
3371        buffer.mark_for_deletion(Operation::Infallible);
3372    }
3373
3374    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
3375    fn DeleteFramebuffer(&self, framebuffer: Option<&WebGLFramebuffer>) {
3376        if let Some(framebuffer) = framebuffer {
3377            // https://immersive-web.github.io/webxr/#opaque-framebuffer
3378            // Can opaque framebuffers be deleted?
3379            // https://github.com/immersive-web/webxr/issues/855
3380            if let Err(error) = framebuffer.validate_transparent() {
3381                return self.webgl_error(error);
3382            }
3383            if let Err(error) = self.validate_ownership(framebuffer) {
3384                return self.webgl_error(error);
3385            }
3386            if let Some(bound_object) = self.bound_draw_framebuffer.get() &&
3387                bound_object.id() == framebuffer.id()
3388            {
3389                self.bound_draw_framebuffer.set(None);
3390                self.send_command(WebGLCommand::BindFramebuffer(
3391                    framebuffer.target().unwrap(),
3392                    WebGLFramebufferBindingRequest::Default,
3393                ));
3394            }
3395            framebuffer.delete(Operation::Infallible)
3396        }
3397    }
3398
3399    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
3400    fn DeleteRenderbuffer(&self, renderbuffer: Option<&WebGLRenderbuffer>) {
3401        if let Some(renderbuffer) = renderbuffer {
3402            if let Err(error) = self.validate_ownership(renderbuffer) {
3403                return self.webgl_error(error);
3404            }
3405            if let Some(bound_object) = self.bound_renderbuffer.get() &&
3406                bound_object.id() == renderbuffer.id()
3407            {
3408                self.bound_renderbuffer.set(None);
3409                self.send_command(WebGLCommand::BindRenderbuffer(
3410                    constants::RENDERBUFFER,
3411                    None,
3412                ));
3413            }
3414            renderbuffer.delete(Operation::Infallible)
3415        }
3416    }
3417
3418    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
3419    fn DeleteTexture(&self, texture: Option<&WebGLTexture>) {
3420        if let Some(texture) = texture {
3421            if let Err(error) = self.validate_ownership(texture) {
3422                return self.webgl_error(error);
3423            }
3424
3425            // From the GLES 2.0.25 spec, page 85:
3426            //
3427            //     "If a texture that is currently bound to one of the targets
3428            //      TEXTURE_2D, or TEXTURE_CUBE_MAP is deleted, it is as though
3429            //      BindTexture had been executed with the same target and texture
3430            //      zero."
3431            //
3432            // The same texture may be bound to multiple texture units.
3433            let mut active_unit_enum = self.textures.active_unit_enum();
3434            for (unit_enum, slot) in self.textures.iter() {
3435                if let Some(target) = slot.unbind(texture) {
3436                    if unit_enum != active_unit_enum {
3437                        self.send_command(WebGLCommand::ActiveTexture(unit_enum));
3438                        active_unit_enum = unit_enum;
3439                    }
3440                    self.send_command(WebGLCommand::BindTexture(target, None));
3441                }
3442            }
3443
3444            // Restore bound texture unit if it has been changed.
3445            if active_unit_enum != self.textures.active_unit_enum() {
3446                self.send_command(WebGLCommand::ActiveTexture(
3447                    self.textures.active_unit_enum(),
3448                ));
3449            }
3450
3451            texture.delete(Operation::Infallible)
3452        }
3453    }
3454
3455    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3456    fn DeleteProgram(&self, program: Option<&WebGLProgram>) {
3457        if let Some(program) = program {
3458            if let Err(error) = self.validate_ownership(program) {
3459                return self.webgl_error(error);
3460            }
3461            program.mark_for_deletion(Operation::Infallible)
3462        }
3463    }
3464
3465    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3466    fn DeleteShader(&self, shader: Option<&WebGLShader>) {
3467        if let Some(shader) = shader {
3468            if let Err(error) = self.validate_ownership(shader) {
3469                return self.webgl_error(error);
3470            }
3471            shader.mark_for_deletion(Operation::Infallible)
3472        }
3473    }
3474
3475    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11>
3476    fn DrawArrays(&self, cx: &mut JSContext, mode: u32, first: i32, count: i32) {
3477        if let Err(error) = self.draw_arrays_instanced(cx, mode, first, count, 1) {
3478            self.webgl_error(error);
3479        }
3480    }
3481
3482    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11>
3483    fn DrawElements(&self, cx: &mut JSContext, mode: u32, count: i32, type_: u32, offset: i64) {
3484        if let Err(error) = self.draw_elements_instanced(cx, mode, count, type_, offset, 1) {
3485            self.webgl_error(error);
3486        }
3487    }
3488
3489    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3490    fn EnableVertexAttribArray(&self, cx: &mut JSContext, attrib_id: u32) {
3491        if attrib_id >= self.limits.max_vertex_attribs {
3492            return self.webgl_error(InvalidValue);
3493        }
3494        match self.webgl_version() {
3495            WebGLVersion::WebGL1 => self
3496                .current_vao(cx)
3497                .enabled_vertex_attrib_array(attrib_id, true),
3498            WebGLVersion::WebGL2 => self
3499                .current_vao_webgl2(cx)
3500                .enabled_vertex_attrib_array(attrib_id, true),
3501        };
3502        self.send_command(WebGLCommand::EnableVertexAttribArray(attrib_id));
3503    }
3504
3505    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3506    fn DisableVertexAttribArray(&self, cx: &mut JSContext, attrib_id: u32) {
3507        if attrib_id >= self.limits.max_vertex_attribs {
3508            return self.webgl_error(InvalidValue);
3509        }
3510        match self.webgl_version() {
3511            WebGLVersion::WebGL1 => self
3512                .current_vao(cx)
3513                .enabled_vertex_attrib_array(attrib_id, false),
3514            WebGLVersion::WebGL2 => self
3515                .current_vao_webgl2(cx)
3516                .enabled_vertex_attrib_array(attrib_id, false),
3517        };
3518        self.send_command(WebGLCommand::DisableVertexAttribArray(attrib_id));
3519    }
3520
3521    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3522    fn GetActiveUniform(
3523        &self,
3524        cx: &mut JSContext,
3525        program: &WebGLProgram,
3526        index: u32,
3527    ) -> Option<DomRoot<WebGLActiveInfo>> {
3528        if let Err(error) = self.validate_ownership(program) {
3529            self.webgl_error(error);
3530            return None;
3531        }
3532        match program.get_active_uniform(cx, index) {
3533            Ok(ret) => Some(ret),
3534            Err(e) => {
3535                self.webgl_error(e);
3536                None
3537            },
3538        }
3539    }
3540
3541    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3542    fn GetActiveAttrib(
3543        &self,
3544        cx: &mut JSContext,
3545        program: &WebGLProgram,
3546        index: u32,
3547    ) -> Option<DomRoot<WebGLActiveInfo>> {
3548        if let Err(error) = self.validate_ownership(program) {
3549            self.webgl_error(error);
3550            return None;
3551        }
3552        match program.get_active_attrib(cx, index) {
3553            Ok(info) => Some(info),
3554            Err(error) => {
3555                self.webgl_error(error);
3556                None
3557            },
3558        }
3559    }
3560
3561    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3562    fn GetAttribLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 {
3563        if let Err(error) = self.validate_ownership(program) {
3564            self.webgl_error(error);
3565            return -1;
3566        }
3567        match program.get_attrib_location(name) {
3568            Ok(location) => location,
3569            Err(error) => {
3570                self.webgl_error(error);
3571                -1
3572            },
3573        }
3574    }
3575
3576    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
3577    fn GetFramebufferAttachmentParameter(
3578        &self,
3579        cx: &mut JSContext,
3580        target: u32,
3581        attachment: u32,
3582        pname: u32,
3583        mut retval: MutableHandleValue,
3584    ) {
3585        // Check if currently bound framebuffer is non-zero as per spec.
3586        if let Some(fb) = self.bound_draw_framebuffer.get() {
3587            // Opaque framebuffers cannot have their attachments inspected
3588            // https://immersive-web.github.io/webxr/#opaque-framebuffer
3589            if let Err(error) = fb.validate_transparent() {
3590                self.webgl_error(error);
3591                return retval.set(NullValue());
3592            }
3593        } else {
3594            self.webgl_error(InvalidOperation);
3595            return retval.set(NullValue());
3596        }
3597
3598        // Note: commented out stuff is for the WebGL2 standard.
3599        let target_matches = match target {
3600            // constants::READ_FRAMEBUFFER |
3601            // constants::DRAW_FRAMEBUFFER => true,
3602            constants::FRAMEBUFFER => true,
3603            _ => false,
3604        };
3605        let attachment_matches = match attachment {
3606            // constants::MAX_COLOR_ATTACHMENTS ... gl::COLOR_ATTACHMENT0 |
3607            // constants::BACK |
3608            constants::COLOR_ATTACHMENT0 |
3609            constants::DEPTH_STENCIL_ATTACHMENT |
3610            constants::DEPTH_ATTACHMENT |
3611            constants::STENCIL_ATTACHMENT => true,
3612            _ => false,
3613        };
3614        let pname_matches = match pname {
3615            // constants::FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE |
3616            // constants::FRAMEBUFFER_ATTACHMENT_BLUE_SIZE |
3617            // constants::FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING |
3618            // constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE |
3619            // constants::FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE |
3620            // constants::FRAMEBUFFER_ATTACHMENT_GREEN_SIZE |
3621            // constants::FRAMEBUFFER_ATTACHMENT_RED_SIZE |
3622            // constants::FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE |
3623            // constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER |
3624            constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME |
3625            constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE |
3626            constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE |
3627            constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL => true,
3628            _ => false,
3629        };
3630
3631        let bound_attachment_matches = match self
3632            .bound_draw_framebuffer
3633            .get()
3634            .unwrap()
3635            .attachment(attachment)
3636        {
3637            Some(attachment_root) => match attachment_root {
3638                WebGLFramebufferAttachmentRoot::Renderbuffer(_) => matches!(
3639                    pname,
3640                    constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE |
3641                        constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME
3642                ),
3643                WebGLFramebufferAttachmentRoot::Texture(_) => matches!(
3644                    pname,
3645                    constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE |
3646                        constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME |
3647                        constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL |
3648                        constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE
3649                ),
3650            },
3651            _ => matches!(pname, constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE),
3652        };
3653
3654        if !target_matches || !attachment_matches || !pname_matches || !bound_attachment_matches {
3655            self.webgl_error(InvalidEnum);
3656            return retval.set(NullValue());
3657        }
3658
3659        // From the GLES2 spec:
3660        //
3661        //     If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE,
3662        //     then querying any other pname will generate INVALID_ENUM.
3663        //
3664        // otherwise, return `WebGLRenderbuffer` or `WebGLTexture` dom object
3665        if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME {
3666            // if fb is None, an INVALID_OPERATION is returned
3667            // at the beggining of the function, so `.unwrap()` will never panic
3668            let fb = self.bound_draw_framebuffer.get().unwrap();
3669            if let Some(webgl_attachment) = fb.attachment(attachment) {
3670                match webgl_attachment {
3671                    WebGLFramebufferAttachmentRoot::Renderbuffer(rb) => {
3672                        rb.to_jsval(cx, retval);
3673                        return;
3674                    },
3675                    WebGLFramebufferAttachmentRoot::Texture(texture) => {
3676                        texture.to_jsval(cx, retval);
3677                        return;
3678                    },
3679                }
3680            }
3681            self.webgl_error(InvalidEnum);
3682            return retval.set(NullValue());
3683        }
3684
3685        let (sender, receiver) = webgl_channel().unwrap();
3686        self.send_command(WebGLCommand::GetFramebufferAttachmentParameter(
3687            target, attachment, pname, sender,
3688        ));
3689
3690        retval.set(Int32Value(receiver.recv().unwrap()))
3691    }
3692
3693    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
3694    fn GetRenderbufferParameter(
3695        &self,
3696        _cx: &mut JSContext,
3697        target: u32,
3698        pname: u32,
3699        mut retval: MutableHandleValue,
3700    ) {
3701        // We do not check to see if the renderbuffer came from an opaque framebuffer
3702        // https://github.com/immersive-web/webxr/issues/862
3703        let target_matches = target == constants::RENDERBUFFER;
3704
3705        let pname_matches = matches!(
3706            pname,
3707            constants::RENDERBUFFER_WIDTH |
3708                constants::RENDERBUFFER_HEIGHT |
3709                constants::RENDERBUFFER_INTERNAL_FORMAT |
3710                constants::RENDERBUFFER_RED_SIZE |
3711                constants::RENDERBUFFER_GREEN_SIZE |
3712                constants::RENDERBUFFER_BLUE_SIZE |
3713                constants::RENDERBUFFER_ALPHA_SIZE |
3714                constants::RENDERBUFFER_DEPTH_SIZE |
3715                constants::RENDERBUFFER_STENCIL_SIZE
3716        );
3717
3718        if !target_matches || !pname_matches {
3719            self.webgl_error(InvalidEnum);
3720            return retval.set(NullValue());
3721        }
3722
3723        if self.bound_renderbuffer.get().is_none() {
3724            self.webgl_error(InvalidOperation);
3725            return retval.set(NullValue());
3726        }
3727
3728        let result = if pname == constants::RENDERBUFFER_INTERNAL_FORMAT {
3729            let rb = self.bound_renderbuffer.get().unwrap();
3730            rb.internal_format() as i32
3731        } else {
3732            let (sender, receiver) = webgl_channel().unwrap();
3733            self.send_command(WebGLCommand::GetRenderbufferParameter(
3734                target, pname, sender,
3735            ));
3736            receiver.recv().unwrap()
3737        };
3738
3739        retval.set(Int32Value(result))
3740    }
3741
3742    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3743    fn GetProgramInfoLog(&self, program: &WebGLProgram) -> Option<DOMString> {
3744        if let Err(error) = self.validate_ownership(program) {
3745            self.webgl_error(error);
3746            return None;
3747        }
3748        match program.get_info_log() {
3749            Ok(value) => Some(DOMString::from(value)),
3750            Err(e) => {
3751                self.webgl_error(e);
3752                None
3753            },
3754        }
3755    }
3756
3757    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3758    fn GetProgramParameter(
3759        &self,
3760        _cx: &mut JSContext,
3761        program: &WebGLProgram,
3762        param: u32,
3763        mut retval: MutableHandleValue,
3764    ) {
3765        if let Err(error) = self.validate_ownership(program) {
3766            self.webgl_error(error);
3767            return retval.set(NullValue());
3768        }
3769        if program.is_deleted() {
3770            self.webgl_error(InvalidOperation);
3771            return retval.set(NullValue());
3772        }
3773        retval.set(match param {
3774            constants::DELETE_STATUS => BooleanValue(program.is_marked_for_deletion()),
3775            constants::LINK_STATUS => BooleanValue(program.is_linked()),
3776            constants::VALIDATE_STATUS => {
3777                // FIXME(nox): This could be cached on the DOM side when we call validateProgram
3778                // but I'm not sure when the value should be reset.
3779                let (sender, receiver) = webgl_channel().unwrap();
3780                self.send_command(WebGLCommand::GetProgramValidateStatus(program.id(), sender));
3781                BooleanValue(receiver.recv().unwrap())
3782            },
3783            constants::ATTACHED_SHADERS => {
3784                // FIXME(nox): This allocates a vector and roots a couple of shaders for nothing.
3785                Int32Value(
3786                    program
3787                        .attached_shaders()
3788                        .map(|shaders| shaders.len() as i32)
3789                        .unwrap_or(0),
3790                )
3791            },
3792            constants::ACTIVE_ATTRIBUTES => Int32Value(program.active_attribs().len() as i32),
3793            constants::ACTIVE_UNIFORMS => Int32Value(program.active_uniforms().len() as i32),
3794            _ => {
3795                self.webgl_error(InvalidEnum);
3796                NullValue()
3797            },
3798        })
3799    }
3800
3801    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3802    fn GetShaderInfoLog(&self, shader: &WebGLShader) -> Option<DOMString> {
3803        if let Err(error) = self.validate_ownership(shader) {
3804            self.webgl_error(error);
3805            return None;
3806        }
3807        Some(shader.info_log())
3808    }
3809
3810    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3811    fn GetShaderParameter(
3812        &self,
3813        _cx: &mut JSContext,
3814        shader: &WebGLShader,
3815        param: u32,
3816        mut retval: MutableHandleValue,
3817    ) {
3818        if let Err(error) = self.validate_ownership(shader) {
3819            self.webgl_error(error);
3820            return retval.set(NullValue());
3821        }
3822        if shader.is_deleted() {
3823            self.webgl_error(InvalidValue);
3824            return retval.set(NullValue());
3825        }
3826        retval.set(match param {
3827            constants::DELETE_STATUS => BooleanValue(shader.is_marked_for_deletion()),
3828            constants::COMPILE_STATUS => BooleanValue(shader.successfully_compiled()),
3829            constants::SHADER_TYPE => UInt32Value(shader.gl_type()),
3830            _ => {
3831                self.webgl_error(InvalidEnum);
3832                NullValue()
3833            },
3834        })
3835    }
3836
3837    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3838    fn GetShaderPrecisionFormat(
3839        &self,
3840        cx: &mut JSContext,
3841        shader_type: u32,
3842        precision_type: u32,
3843    ) -> Option<DomRoot<WebGLShaderPrecisionFormat>> {
3844        match shader_type {
3845            constants::FRAGMENT_SHADER | constants::VERTEX_SHADER => (),
3846            _ => {
3847                self.webgl_error(InvalidEnum);
3848                return None;
3849            },
3850        }
3851
3852        match precision_type {
3853            constants::LOW_FLOAT |
3854            constants::MEDIUM_FLOAT |
3855            constants::HIGH_FLOAT |
3856            constants::LOW_INT |
3857            constants::MEDIUM_INT |
3858            constants::HIGH_INT => (),
3859            _ => {
3860                self.webgl_error(InvalidEnum);
3861                return None;
3862            },
3863        }
3864
3865        let (sender, receiver) = webgl_channel().unwrap();
3866        self.send_command(WebGLCommand::GetShaderPrecisionFormat(
3867            shader_type,
3868            precision_type,
3869            sender,
3870        ));
3871
3872        let (range_min, range_max, precision) = receiver.recv().unwrap();
3873        Some(WebGLShaderPrecisionFormat::new(
3874            cx,
3875            self.global().as_window(),
3876            range_min,
3877            range_max,
3878            precision,
3879        ))
3880    }
3881
3882    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3883    fn GetUniformLocation(
3884        &self,
3885        cx: &mut JSContext,
3886        program: &WebGLProgram,
3887        name: DOMString,
3888    ) -> Option<DomRoot<WebGLUniformLocation>> {
3889        if let Err(error) = self.validate_ownership(program) {
3890            self.webgl_error(error);
3891            return None;
3892        }
3893        match program.get_uniform_location(cx, name) {
3894            Ok(location) => location,
3895            Err(error) => {
3896                self.webgl_error(error);
3897                None
3898            },
3899        }
3900    }
3901
3902    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
3903    fn GetVertexAttrib(
3904        &self,
3905        cx: &mut JSContext,
3906        index: u32,
3907        param: u32,
3908        mut retval: MutableHandleValue,
3909    ) {
3910        let mut get_attrib = |cx: &mut JSContext, data: Ref<'_, VertexAttribData>| {
3911            if param == constants::CURRENT_VERTEX_ATTRIB {
3912                let attrib = self.current_vertex_attribs.borrow()[index as usize];
3913                match attrib {
3914                    VertexAttrib::Float(x, y, z, w) => {
3915                        let value = [x, y, z, w];
3916                        rooted!(&in(cx) let mut result = ptr::null_mut::<JSObject>());
3917                        create_buffer_source::<Float32>(cx, &value, result.handle_mut()).unwrap();
3918                        return retval.set(ObjectValue(result.get()));
3919                    },
3920                    VertexAttrib::Int(x, y, z, w) => {
3921                        let value = [x, y, z, w];
3922                        rooted!(&in(cx) let mut result = ptr::null_mut::<JSObject>());
3923                        create_buffer_source::<Int32>(cx, &value, result.handle_mut()).unwrap();
3924                        return retval.set(ObjectValue(result.get()));
3925                    },
3926                    VertexAttrib::Uint(x, y, z, w) => {
3927                        let value = [x, y, z, w];
3928                        rooted!(&in(cx) let mut result = ptr::null_mut::<JSObject>());
3929                        create_buffer_source::<Uint32>(cx, &value, result.handle_mut()).unwrap();
3930                        return retval.set(ObjectValue(result.get()));
3931                    },
3932                };
3933            }
3934            if !self
3935                .extension_manager
3936                .is_get_vertex_attrib_name_enabled(param)
3937            {
3938                self.webgl_error(WebGLError::InvalidEnum);
3939                return retval.set(NullValue());
3940            }
3941
3942            match param {
3943                constants::VERTEX_ATTRIB_ARRAY_ENABLED => {
3944                    retval.set(BooleanValue(data.enabled_as_array))
3945                },
3946                constants::VERTEX_ATTRIB_ARRAY_SIZE => retval.set(Int32Value(data.size as i32)),
3947                constants::VERTEX_ATTRIB_ARRAY_TYPE => retval.set(Int32Value(data.type_ as i32)),
3948                constants::VERTEX_ATTRIB_ARRAY_NORMALIZED => {
3949                    retval.set(BooleanValue(data.kind.is_normalized()))
3950                },
3951                constants::VERTEX_ATTRIB_ARRAY_STRIDE => retval.set(Int32Value(data.stride as i32)),
3952                constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING => {
3953                    if let Some(buffer) = data.buffer() {
3954                        buffer.to_jsval(cx, retval.reborrow());
3955                    } else {
3956                        retval.set(NullValue());
3957                    }
3958                },
3959                ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE => {
3960                    retval.set(UInt32Value(data.divisor))
3961                },
3962                WebGL2RenderingContextConstants::VERTEX_ATTRIB_ARRAY_INTEGER
3963                    if self.webgl_version() == WebGLVersion::WebGL2 =>
3964                {
3965                    retval.set(BooleanValue(data.kind.is_integer()))
3966                },
3967                _ => {
3968                    self.webgl_error(InvalidEnum);
3969                    retval.set(NullValue())
3970                },
3971            }
3972        };
3973
3974        match self.webgl_version() {
3975            WebGLVersion::WebGL1 => {
3976                let current_vao = self.current_vao(cx);
3977                let Some(data) = current_vao.get_vertex_attrib(index) else {
3978                    self.webgl_error(InvalidValue);
3979                    return retval.set(NullValue());
3980                };
3981                get_attrib(cx, data)
3982            },
3983            WebGLVersion::WebGL2 => {
3984                let current_vao = self.current_vao_webgl2(cx);
3985                let Some(data) = current_vao.get_vertex_attrib(index) else {
3986                    self.webgl_error(InvalidValue);
3987                    return retval.set(NullValue());
3988                };
3989                get_attrib(cx, data)
3990            },
3991        }
3992    }
3993
3994    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
3995    fn GetVertexAttribOffset(&self, cx: &mut JSContext, index: u32, pname: u32) -> i64 {
3996        if pname != constants::VERTEX_ATTRIB_ARRAY_POINTER {
3997            self.webgl_error(InvalidEnum);
3998            return 0;
3999        }
4000        match self.webgl_version() {
4001            WebGLVersion::WebGL1 => {
4002                let current_vao = self.current_vao(cx);
4003                let Some(data) = current_vao.get_vertex_attrib(index) else {
4004                    self.webgl_error(InvalidValue);
4005                    return 0;
4006                };
4007                data.offset as i64
4008            },
4009            WebGLVersion::WebGL2 => {
4010                let current_vao = self.current_vao_webgl2(cx);
4011                let Some(data) = current_vao.get_vertex_attrib(index) else {
4012                    self.webgl_error(InvalidValue);
4013                    return 0;
4014                };
4015                data.offset as i64
4016            },
4017        }
4018    }
4019
4020    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4021    fn Hint(&self, target: u32, mode: u32) {
4022        if target != constants::GENERATE_MIPMAP_HINT &&
4023            !self.extension_manager.is_hint_target_enabled(target)
4024        {
4025            return self.webgl_error(InvalidEnum);
4026        }
4027
4028        match mode {
4029            constants::FASTEST | constants::NICEST | constants::DONT_CARE => (),
4030
4031            _ => return self.webgl_error(InvalidEnum),
4032        }
4033
4034        self.send_command(WebGLCommand::Hint(target, mode));
4035    }
4036
4037    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
4038    fn IsBuffer(&self, buffer: Option<&WebGLBuffer>) -> bool {
4039        buffer.is_some_and(|buf| {
4040            self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_deleted()
4041        })
4042    }
4043
4044    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4045    fn IsEnabled(&self, cap: u32) -> bool {
4046        match self.capabilities.is_enabled(cap) {
4047            Ok(enabled) => enabled,
4048            Err(error) => {
4049                self.webgl_error(error);
4050                false
4051            },
4052        }
4053    }
4054
4055    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
4056    fn IsFramebuffer(&self, frame_buffer: Option<&WebGLFramebuffer>) -> bool {
4057        frame_buffer.is_some_and(|buf| {
4058            self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_deleted()
4059        })
4060    }
4061
4062    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4063    fn IsProgram(&self, program: Option<&WebGLProgram>) -> bool {
4064        program.is_some_and(|p| self.validate_ownership(p).is_ok() && !p.is_deleted())
4065    }
4066
4067    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
4068    fn IsRenderbuffer(&self, render_buffer: Option<&WebGLRenderbuffer>) -> bool {
4069        render_buffer.is_some_and(|buf| {
4070            self.validate_ownership(buf).is_ok() && buf.ever_bound() && !buf.is_deleted()
4071        })
4072    }
4073
4074    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4075    fn IsShader(&self, shader: Option<&WebGLShader>) -> bool {
4076        shader.is_some_and(|s| self.validate_ownership(s).is_ok() && !s.is_deleted())
4077    }
4078
4079    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
4080    fn IsTexture(&self, texture: Option<&WebGLTexture>) -> bool {
4081        texture.is_some_and(|tex| {
4082            self.validate_ownership(tex).is_ok() && tex.target().is_some() && !tex.is_invalid()
4083        })
4084    }
4085
4086    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4087    fn LineWidth(&self, width: f32) {
4088        if width.is_nan() || width <= 0f32 {
4089            return self.webgl_error(InvalidValue);
4090        }
4091
4092        self.send_command(WebGLCommand::LineWidth(width))
4093    }
4094
4095    /// NOTE: Usage of this function could affect rendering while we keep using
4096    ///   readback to render to the page.
4097    ///
4098    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4099    fn PixelStorei(&self, param_name: u32, param_value: i32) {
4100        let mut texture_settings = self.texture_unpacking_settings.get();
4101        match param_name {
4102            constants::UNPACK_FLIP_Y_WEBGL => {
4103                texture_settings.set(TextureUnpacking::FLIP_Y_AXIS, param_value != 0);
4104            },
4105            constants::UNPACK_PREMULTIPLY_ALPHA_WEBGL => {
4106                texture_settings.set(TextureUnpacking::PREMULTIPLY_ALPHA, param_value != 0);
4107            },
4108            constants::UNPACK_COLORSPACE_CONVERSION_WEBGL => {
4109                let convert = match param_value as u32 {
4110                    constants::BROWSER_DEFAULT_WEBGL => true,
4111                    constants::NONE => false,
4112                    _ => return self.webgl_error(InvalidEnum),
4113                };
4114                texture_settings.set(TextureUnpacking::CONVERT_COLORSPACE, convert);
4115            },
4116            constants::UNPACK_ALIGNMENT => {
4117                match param_value {
4118                    1 | 2 | 4 | 8 => (),
4119                    _ => return self.webgl_error(InvalidValue),
4120                }
4121                self.texture_unpacking_alignment.set(param_value as u32);
4122                return;
4123            },
4124            constants::PACK_ALIGNMENT => {
4125                match param_value {
4126                    1 | 2 | 4 | 8 => (),
4127                    _ => return self.webgl_error(InvalidValue),
4128                }
4129                // We never actually change the actual value on the GL side
4130                // because it's better to receive the pixels without the padding
4131                // and then write the result at the right place in ReadPixels.
4132                self.texture_packing_alignment.set(param_value as u8);
4133                return;
4134            },
4135            _ => return self.webgl_error(InvalidEnum),
4136        }
4137        self.texture_unpacking_settings.set(texture_settings);
4138    }
4139
4140    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4141    fn PolygonOffset(&self, factor: f32, units: f32) {
4142        self.send_command(WebGLCommand::PolygonOffset(factor, units))
4143    }
4144
4145    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12
4146    fn ReadPixels(
4147        &self,
4148        no_gc: &NoGC,
4149        x: i32,
4150        y: i32,
4151        width: i32,
4152        height: i32,
4153        format: u32,
4154        pixel_type: u32,
4155        mut pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>,
4156    ) {
4157        if let Err(error) = self.validate_read_framebuffer() {
4158            return self.webgl_error(error);
4159        }
4160
4161        let Some(pixels) = pixels.as_mut() else {
4162            return self.webgl_error(InvalidValue);
4163        };
4164
4165        if width < 0 || height < 0 {
4166            return self.webgl_error(InvalidValue);
4167        }
4168
4169        if format != constants::RGBA || pixel_type != constants::UNSIGNED_BYTE {
4170            return self.webgl_error(InvalidOperation);
4171        }
4172
4173        if pixels.get_array_type() != Type::Uint8 {
4174            return self.webgl_error(InvalidOperation);
4175        }
4176
4177        let Some((fb_width, fb_height)) = self.get_current_framebuffer_size() else {
4178            return self.webgl_error(InvalidOperation);
4179        };
4180
4181        if width == 0 || height == 0 {
4182            return;
4183        }
4184
4185        let bytes_per_pixel = 4;
4186
4187        let Some(row_len) = width.checked_mul(bytes_per_pixel) else {
4188            return self.webgl_error(InvalidOperation);
4189        };
4190
4191        let pack_alignment = self.texture_packing_alignment.get() as i32;
4192        let dest_padding = match row_len % pack_alignment {
4193            0 => 0,
4194            remainder => pack_alignment - remainder,
4195        };
4196        let dest_stride = row_len + dest_padding;
4197
4198        let Some(full_rows_len) = dest_stride.checked_mul(height - 1) else {
4199            return self.webgl_error(InvalidOperation);
4200        };
4201
4202        let Some(required_dest_len) = full_rows_len.checked_add(row_len) else {
4203            return self.webgl_error(InvalidOperation);
4204        };
4205
4206        let dest = pixels.as_mut_slice_safe(no_gc).unwrap_or(&mut []);
4207        if dest.len() < required_dest_len as usize {
4208            return self.webgl_error(InvalidOperation);
4209        }
4210
4211        let src_origin = Point2D::new(x, y);
4212        let src_size = Size2D::new(width as u32, height as u32);
4213        let fb_size = Size2D::new(fb_width as u32, fb_height as u32);
4214        let src_rect = match pixels::clip(src_origin, src_size.to_u32(), fb_size.to_u32()) {
4215            Some(rect) => rect,
4216            None => return,
4217        };
4218
4219        // Note: we're casting a Rect<u64> back into a Rect<u32> here, but it's okay because
4220        //  it used u32 data types to begin with. It just got converted to Rect<u64> in
4221        //  pixels::clip
4222        let src_rect = src_rect.to_u32();
4223
4224        let mut dest_offset = 0;
4225        if x < 0 {
4226            dest_offset += -x * bytes_per_pixel;
4227        }
4228        if y < 0 {
4229            dest_offset += -y * row_len;
4230        }
4231
4232        let (sender, receiver) = generic_channel::channel().unwrap();
4233        self.send_command(WebGLCommand::ReadPixels(
4234            src_rect, format, pixel_type, sender,
4235        ));
4236        let (src, _) = receiver.recv().unwrap();
4237
4238        let src_row_len = src_rect.size.width as usize * bytes_per_pixel as usize;
4239        for i in 0..src_rect.size.height {
4240            let dest_start = dest_offset as usize + i as usize * dest_stride as usize;
4241            let dest_end = dest_start + src_row_len;
4242            let src_start = i as usize * src_row_len;
4243            let src_end = src_start + src_row_len;
4244            dest[dest_start..dest_end].copy_from_slice(&src[src_start..src_end]);
4245        }
4246    }
4247
4248    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4249    fn SampleCoverage(&self, value: f32, invert: bool) {
4250        self.send_command(WebGLCommand::SampleCoverage(value, invert));
4251    }
4252
4253    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4>
4254    fn Scissor(&self, x: i32, y: i32, width: i32, height: i32) {
4255        if width < 0 || height < 0 {
4256            return self.webgl_error(InvalidValue);
4257        }
4258
4259        let width = width as u32;
4260        let height = height as u32;
4261
4262        self.current_scissor.set((x, y, width, height));
4263        self.send_command(WebGLCommand::Scissor(x, y, width, height));
4264    }
4265
4266    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4267    fn StencilFunc(&self, func: u32, ref_: i32, mask: u32) {
4268        match func {
4269            constants::NEVER |
4270            constants::LESS |
4271            constants::EQUAL |
4272            constants::LEQUAL |
4273            constants::GREATER |
4274            constants::NOTEQUAL |
4275            constants::GEQUAL |
4276            constants::ALWAYS => self.send_command(WebGLCommand::StencilFunc(func, ref_, mask)),
4277            _ => self.webgl_error(InvalidEnum),
4278        }
4279    }
4280
4281    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4282    fn StencilFuncSeparate(&self, face: u32, func: u32, ref_: i32, mask: u32) {
4283        match face {
4284            constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => (),
4285            _ => return self.webgl_error(InvalidEnum),
4286        }
4287
4288        match func {
4289            constants::NEVER |
4290            constants::LESS |
4291            constants::EQUAL |
4292            constants::LEQUAL |
4293            constants::GREATER |
4294            constants::NOTEQUAL |
4295            constants::GEQUAL |
4296            constants::ALWAYS => {
4297                self.send_command(WebGLCommand::StencilFuncSeparate(face, func, ref_, mask))
4298            },
4299            _ => self.webgl_error(InvalidEnum),
4300        }
4301    }
4302
4303    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4304    fn StencilMask(&self, mask: u32) {
4305        self.send_command(WebGLCommand::StencilMask(mask))
4306    }
4307
4308    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4309    fn StencilMaskSeparate(&self, face: u32, mask: u32) {
4310        match face {
4311            constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => {
4312                self.send_command(WebGLCommand::StencilMaskSeparate(face, mask))
4313            },
4314            _ => self.webgl_error(InvalidEnum),
4315        };
4316    }
4317
4318    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4319    fn StencilOp(&self, fail: u32, zfail: u32, zpass: u32) {
4320        if self.validate_stencil_actions(fail) &&
4321            self.validate_stencil_actions(zfail) &&
4322            self.validate_stencil_actions(zpass)
4323        {
4324            self.send_command(WebGLCommand::StencilOp(fail, zfail, zpass));
4325        } else {
4326            self.webgl_error(InvalidEnum)
4327        }
4328    }
4329
4330    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
4331    fn StencilOpSeparate(&self, face: u32, fail: u32, zfail: u32, zpass: u32) {
4332        match face {
4333            constants::FRONT | constants::BACK | constants::FRONT_AND_BACK => (),
4334            _ => return self.webgl_error(InvalidEnum),
4335        }
4336
4337        if self.validate_stencil_actions(fail) &&
4338            self.validate_stencil_actions(zfail) &&
4339            self.validate_stencil_actions(zpass)
4340        {
4341            self.send_command(WebGLCommand::StencilOpSeparate(face, fail, zfail, zpass))
4342        } else {
4343            self.webgl_error(InvalidEnum)
4344        }
4345    }
4346
4347    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4348    fn LinkProgram(&self, program: &WebGLProgram) {
4349        if let Err(error) = self.validate_ownership(program) {
4350            return self.webgl_error(error);
4351        }
4352        if program.is_deleted() {
4353            return self.webgl_error(InvalidValue);
4354        }
4355        if let Err(error) = program.link() {
4356            self.webgl_error(error);
4357        }
4358    }
4359
4360    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4361    fn ShaderSource(&self, shader: &WebGLShader, source: DOMString) {
4362        if let Err(error) = self.validate_ownership(shader) {
4363            return self.webgl_error(error);
4364        }
4365        shader.set_source(source)
4366    }
4367
4368    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4369    fn GetShaderSource(&self, shader: &WebGLShader) -> Option<DOMString> {
4370        if let Err(error) = self.validate_ownership(shader) {
4371            self.webgl_error(error);
4372            return None;
4373        }
4374        Some(shader.source())
4375    }
4376
4377    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4378    fn Uniform1f(&self, location: Option<&WebGLUniformLocation>, val: f32) {
4379        self.with_location(location, |location| {
4380            match location.type_() {
4381                constants::BOOL | constants::FLOAT => {},
4382                _ => return Err(InvalidOperation),
4383            }
4384            self.send_command(WebGLCommand::Uniform1f(location.id(), val));
4385            Ok(())
4386        });
4387    }
4388
4389    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4390    fn Uniform1i(&self, location: Option<&WebGLUniformLocation>, val: i32) {
4391        self.with_location(location, |location| {
4392            match location.type_() {
4393                constants::BOOL | constants::INT => {},
4394                constants::SAMPLER_2D |
4395                WebGL2RenderingContextConstants::SAMPLER_3D |
4396                WebGL2RenderingContextConstants::SAMPLER_2D_ARRAY |
4397                constants::SAMPLER_CUBE => {
4398                    if val < 0 || val as u32 >= self.limits.max_combined_texture_image_units {
4399                        return Err(InvalidValue);
4400                    }
4401                },
4402                _ => return Err(InvalidOperation),
4403            }
4404            self.send_command(WebGLCommand::Uniform1i(location.id(), val));
4405            Ok(())
4406        });
4407    }
4408
4409    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4410    fn Uniform1iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) {
4411        self.uniform1iv(location, val, 0, 0)
4412    }
4413
4414    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4415    fn Uniform1fv(
4416        &self,
4417        location: Option<&WebGLUniformLocation>,
4418        val: Float32ArrayOrUnrestrictedFloatSequence,
4419    ) {
4420        self.uniform1fv(location, val, 0, 0)
4421    }
4422
4423    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4424    fn Uniform2f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32) {
4425        self.with_location(location, |location| {
4426            match location.type_() {
4427                constants::BOOL_VEC2 | constants::FLOAT_VEC2 => {},
4428                _ => return Err(InvalidOperation),
4429            }
4430            self.send_command(WebGLCommand::Uniform2f(location.id(), x, y));
4431            Ok(())
4432        });
4433    }
4434
4435    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4436    fn Uniform2fv(
4437        &self,
4438        location: Option<&WebGLUniformLocation>,
4439        val: Float32ArrayOrUnrestrictedFloatSequence,
4440    ) {
4441        self.uniform2fv(location, val, 0, 0)
4442    }
4443
4444    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4445    fn Uniform2i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32) {
4446        self.with_location(location, |location| {
4447            match location.type_() {
4448                constants::BOOL_VEC2 | constants::INT_VEC2 => {},
4449                _ => return Err(InvalidOperation),
4450            }
4451            self.send_command(WebGLCommand::Uniform2i(location.id(), x, y));
4452            Ok(())
4453        });
4454    }
4455
4456    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4457    fn Uniform2iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) {
4458        self.uniform2iv(location, val, 0, 0)
4459    }
4460
4461    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4462    fn Uniform3f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32) {
4463        self.with_location(location, |location| {
4464            match location.type_() {
4465                constants::BOOL_VEC3 | constants::FLOAT_VEC3 => {},
4466                _ => return Err(InvalidOperation),
4467            }
4468            self.send_command(WebGLCommand::Uniform3f(location.id(), x, y, z));
4469            Ok(())
4470        });
4471    }
4472
4473    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4474    fn Uniform3fv(
4475        &self,
4476        location: Option<&WebGLUniformLocation>,
4477        val: Float32ArrayOrUnrestrictedFloatSequence,
4478    ) {
4479        self.uniform3fv(location, val, 0, 0)
4480    }
4481
4482    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4483    fn Uniform3i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32) {
4484        self.with_location(location, |location| {
4485            match location.type_() {
4486                constants::BOOL_VEC3 | constants::INT_VEC3 => {},
4487                _ => return Err(InvalidOperation),
4488            }
4489            self.send_command(WebGLCommand::Uniform3i(location.id(), x, y, z));
4490            Ok(())
4491        });
4492    }
4493
4494    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4495    fn Uniform3iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) {
4496        self.uniform3iv(location, val, 0, 0)
4497    }
4498
4499    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4500    fn Uniform4i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32, w: i32) {
4501        self.with_location(location, |location| {
4502            match location.type_() {
4503                constants::BOOL_VEC4 | constants::INT_VEC4 => {},
4504                _ => return Err(InvalidOperation),
4505            }
4506            self.send_command(WebGLCommand::Uniform4i(location.id(), x, y, z, w));
4507            Ok(())
4508        });
4509    }
4510
4511    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4512    fn Uniform4iv(&self, location: Option<&WebGLUniformLocation>, val: Int32ArrayOrLongSequence) {
4513        self.uniform4iv(location, val, 0, 0)
4514    }
4515
4516    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4517    fn Uniform4f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32, w: f32) {
4518        self.with_location(location, |location| {
4519            match location.type_() {
4520                constants::BOOL_VEC4 | constants::FLOAT_VEC4 => {},
4521                _ => return Err(InvalidOperation),
4522            }
4523            self.send_command(WebGLCommand::Uniform4f(location.id(), x, y, z, w));
4524            Ok(())
4525        });
4526    }
4527
4528    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4529    fn Uniform4fv(
4530        &self,
4531        location: Option<&WebGLUniformLocation>,
4532        val: Float32ArrayOrUnrestrictedFloatSequence,
4533    ) {
4534        self.uniform4fv(location, val, 0, 0)
4535    }
4536
4537    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4538    fn UniformMatrix2fv(
4539        &self,
4540        location: Option<&WebGLUniformLocation>,
4541        transpose: bool,
4542        val: Float32ArrayOrUnrestrictedFloatSequence,
4543    ) {
4544        self.uniform_matrix_2fv(location, transpose, val, 0, 0)
4545    }
4546
4547    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4548    fn UniformMatrix3fv(
4549        &self,
4550        location: Option<&WebGLUniformLocation>,
4551        transpose: bool,
4552        val: Float32ArrayOrUnrestrictedFloatSequence,
4553    ) {
4554        self.uniform_matrix_3fv(location, transpose, val, 0, 0)
4555    }
4556
4557    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4558    fn UniformMatrix4fv(
4559        &self,
4560        location: Option<&WebGLUniformLocation>,
4561        transpose: bool,
4562        val: Float32ArrayOrUnrestrictedFloatSequence,
4563    ) {
4564        self.uniform_matrix_4fv(location, transpose, val, 0, 0)
4565    }
4566
4567    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
4568    fn GetUniform(
4569        &self,
4570        cx: &mut JSContext,
4571        program: &WebGLProgram,
4572        location: &WebGLUniformLocation,
4573        mut rval: MutableHandleValue,
4574    ) {
4575        if let Err(error) = self.uniform_check_program(program, location) {
4576            self.webgl_error(error);
4577            return rval.set(NullValue());
4578        }
4579
4580        let triple = (self, program.id(), location.id());
4581
4582        match location.type_() {
4583            constants::BOOL => rval.set(BooleanValue(uniform_get(
4584                triple,
4585                WebGLCommand::GetUniformBool,
4586            ))),
4587            constants::BOOL_VEC2 => {
4588                uniform_get(triple, WebGLCommand::GetUniformBool2).to_jsval(cx, rval)
4589            },
4590            constants::BOOL_VEC3 => {
4591                uniform_get(triple, WebGLCommand::GetUniformBool3).to_jsval(cx, rval)
4592            },
4593            constants::BOOL_VEC4 => {
4594                uniform_get(triple, WebGLCommand::GetUniformBool4).to_jsval(cx, rval)
4595            },
4596            constants::INT |
4597            constants::SAMPLER_2D |
4598            constants::SAMPLER_CUBE |
4599            WebGL2RenderingContextConstants::SAMPLER_2D_ARRAY |
4600            WebGL2RenderingContextConstants::SAMPLER_3D => {
4601                rval.set(Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt)))
4602            },
4603            constants::INT_VEC2 => {
4604                uniform_typed::<Int32>(cx, &uniform_get(triple, WebGLCommand::GetUniformInt2), rval)
4605            },
4606            constants::INT_VEC3 => {
4607                uniform_typed::<Int32>(cx, &uniform_get(triple, WebGLCommand::GetUniformInt3), rval)
4608            },
4609            constants::INT_VEC4 => {
4610                uniform_typed::<Int32>(cx, &uniform_get(triple, WebGLCommand::GetUniformInt4), rval)
4611            },
4612            constants::FLOAT => rval
4613                .set(DoubleValue(
4614                    uniform_get(triple, WebGLCommand::GetUniformFloat) as f64,
4615                )),
4616            constants::FLOAT_VEC2 => uniform_typed::<Float32>(
4617                cx,
4618                &uniform_get(triple, WebGLCommand::GetUniformFloat2),
4619                rval,
4620            ),
4621            constants::FLOAT_VEC3 => uniform_typed::<Float32>(
4622                cx,
4623                &uniform_get(triple, WebGLCommand::GetUniformFloat3),
4624                rval,
4625            ),
4626            constants::FLOAT_VEC4 | constants::FLOAT_MAT2 => uniform_typed::<Float32>(
4627                cx,
4628                &uniform_get(triple, WebGLCommand::GetUniformFloat4),
4629                rval,
4630            ),
4631            constants::FLOAT_MAT3 => uniform_typed::<Float32>(
4632                cx,
4633                &uniform_get(triple, WebGLCommand::GetUniformFloat9),
4634                rval,
4635            ),
4636            constants::FLOAT_MAT4 => uniform_typed::<Float32>(
4637                cx,
4638                &uniform_get(triple, WebGLCommand::GetUniformFloat16),
4639                rval,
4640            ),
4641            _ => panic!("wrong uniform type"),
4642        }
4643    }
4644
4645    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4646    fn UseProgram(&self, program: Option<&WebGLProgram>) {
4647        if let Some(program) = program {
4648            if let Err(error) = self.validate_ownership(program) {
4649                return self.webgl_error(error);
4650            }
4651            if program.is_deleted() || !program.is_linked() {
4652                return self.webgl_error(InvalidOperation);
4653            }
4654            if program.is_in_use() {
4655                return;
4656            }
4657            program.in_use(true);
4658        }
4659        match self.current_program.get() {
4660            Some(ref current) if program != Some(&**current) => current.in_use(false),
4661            _ => {},
4662        }
4663        self.send_command(WebGLCommand::UseProgram(program.map(|p| p.id())));
4664        self.current_program.set(program);
4665    }
4666
4667    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
4668    fn ValidateProgram(&self, program: &WebGLProgram) {
4669        if let Err(error) = self.validate_ownership(program) {
4670            return self.webgl_error(error);
4671        }
4672        if let Err(e) = program.validate() {
4673            self.webgl_error(e);
4674        }
4675    }
4676
4677    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4678    fn VertexAttrib1f(&self, _cx: &mut JSContext, indx: u32, x: f32) {
4679        self.vertex_attrib(indx, x, 0f32, 0f32, 1f32)
4680    }
4681
4682    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4683    fn VertexAttrib1fv(
4684        &self,
4685        _cx: &mut JSContext,
4686        indx: u32,
4687        v: Float32ArrayOrUnrestrictedFloatSequence,
4688    ) {
4689        let values = match v {
4690            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => {
4691                v.to_vec().unwrap_or_default()
4692            },
4693            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
4694        };
4695        if values.is_empty() {
4696            // https://github.com/KhronosGroup/WebGL/issues/2700
4697            return self.webgl_error(InvalidValue);
4698        }
4699        self.vertex_attrib(indx, values[0], 0f32, 0f32, 1f32);
4700    }
4701
4702    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4703    fn VertexAttrib2f(&self, _cx: &mut JSContext, indx: u32, x: f32, y: f32) {
4704        self.vertex_attrib(indx, x, y, 0f32, 1f32)
4705    }
4706
4707    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4708    fn VertexAttrib2fv(
4709        &self,
4710        _cx: &mut JSContext,
4711        indx: u32,
4712        v: Float32ArrayOrUnrestrictedFloatSequence,
4713    ) {
4714        let values = match v {
4715            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => {
4716                v.to_vec().unwrap_or_default()
4717            },
4718            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
4719        };
4720        if values.len() < 2 {
4721            // https://github.com/KhronosGroup/WebGL/issues/2700
4722            return self.webgl_error(InvalidValue);
4723        }
4724        self.vertex_attrib(indx, values[0], values[1], 0f32, 1f32);
4725    }
4726
4727    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4728    fn VertexAttrib3f(&self, _cx: &mut JSContext, indx: u32, x: f32, y: f32, z: f32) {
4729        self.vertex_attrib(indx, x, y, z, 1f32)
4730    }
4731
4732    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4733    fn VertexAttrib3fv(
4734        &self,
4735        _cx: &mut JSContext,
4736        indx: u32,
4737        v: Float32ArrayOrUnrestrictedFloatSequence,
4738    ) {
4739        let values = match v {
4740            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => {
4741                v.to_vec().unwrap_or_default()
4742            },
4743            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
4744        };
4745        if values.len() < 3 {
4746            // https://github.com/KhronosGroup/WebGL/issues/2700
4747            return self.webgl_error(InvalidValue);
4748        }
4749        self.vertex_attrib(indx, values[0], values[1], values[2], 1f32);
4750    }
4751
4752    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4753    fn VertexAttrib4f(&self, _cx: &mut JSContext, indx: u32, x: f32, y: f32, z: f32, w: f32) {
4754        self.vertex_attrib(indx, x, y, z, w)
4755    }
4756
4757    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4758    fn VertexAttrib4fv(
4759        &self,
4760        _cx: &mut JSContext,
4761        indx: u32,
4762        v: Float32ArrayOrUnrestrictedFloatSequence,
4763    ) {
4764        let values = match v {
4765            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => {
4766                v.to_vec().unwrap_or_default()
4767            },
4768            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
4769        };
4770        if values.len() < 4 {
4771            // https://github.com/KhronosGroup/WebGL/issues/2700
4772            return self.webgl_error(InvalidValue);
4773        }
4774        self.vertex_attrib(indx, values[0], values[1], values[2], values[3]);
4775    }
4776
4777    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
4778    fn VertexAttribPointer(
4779        &self,
4780        cx: &mut JSContext,
4781        index: u32,
4782        size: i32,
4783        type_: u32,
4784        normalized: bool,
4785        stride: i32,
4786        offset: i64,
4787    ) {
4788        let kind = VertexAttribPointerKind::Float { normalized };
4789        let res = match self.webgl_version() {
4790            WebGLVersion::WebGL1 => self
4791                .current_vao(cx)
4792                .vertex_attrib_pointer(index, size, type_, stride, offset, kind),
4793            WebGLVersion::WebGL2 => self
4794                .current_vao_webgl2(cx)
4795                .vertex_attrib_pointer(index, size, type_, stride, offset, kind),
4796        };
4797        if let Err(error) = res {
4798            self.webgl_error(error);
4799        }
4800    }
4801
4802    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4>
4803    fn Viewport(&self, x: i32, y: i32, width: i32, height: i32) {
4804        if width < 0 || height < 0 {
4805            return self.webgl_error(InvalidValue);
4806        }
4807
4808        self.send_command(WebGLCommand::SetViewport(x, y, width, height))
4809    }
4810
4811    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
4812    fn TexImage2D(
4813        &self,
4814        no_gc: &NoGC,
4815        target: u32,
4816        level: i32,
4817        internal_format: i32,
4818        width: i32,
4819        height: i32,
4820        border: i32,
4821        format: u32,
4822        data_type: u32,
4823        pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>,
4824    ) -> ErrorResult {
4825        if !self.extension_manager.is_tex_type_enabled(data_type) {
4826            self.webgl_error(InvalidEnum);
4827            return Ok(());
4828        }
4829
4830        let validator = TexImage2DValidator::new(
4831            self,
4832            target,
4833            level,
4834            internal_format as u32,
4835            width,
4836            height,
4837            border,
4838            format,
4839            data_type,
4840        );
4841
4842        let TexImage2DValidatorResult {
4843            texture,
4844            target,
4845            width,
4846            height,
4847            level,
4848            border,
4849            internal_format,
4850            format,
4851            data_type,
4852        } = match validator.validate() {
4853            Ok(result) => result,
4854            Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
4855        };
4856
4857        if !internal_format.compatible_data_types().contains(&data_type) {
4858            return {
4859                self.webgl_error(InvalidOperation);
4860                Ok(())
4861            };
4862        }
4863        if texture.is_immutable() {
4864            return {
4865                self.webgl_error(InvalidOperation);
4866                Ok(())
4867            };
4868        }
4869
4870        let unpacking_alignment = self.texture_unpacking_alignment.get();
4871
4872        let expected_byte_length = match self.validate_tex_image_2d_data(
4873            width,
4874            height,
4875            format,
4876            data_type,
4877            unpacking_alignment,
4878            pixels.as_ref(),
4879        ) {
4880            Ok(byte_length) => byte_length,
4881            Err(()) => return Ok(()),
4882        };
4883
4884        // If data is null, a buffer of sufficient size
4885        // initialized to 0 is passed.
4886        let buff = match *pixels {
4887            None => GenericSharedMemory::from_byte(0, expected_byte_length as usize),
4888            Some(ref data) => {
4889                let block = match self.unpack_block(
4890                    data.as_slice_safe(no_gc).unwrap_or_default(),
4891                    width,
4892                    height,
4893                    None,
4894                    bytes_per_pixel(format, data_type),
4895                ) {
4896                    Ok(block) => block,
4897                    Err(()) => return Ok(()),
4898                };
4899                GenericSharedMemory::from_bytes(&block)
4900            },
4901        };
4902
4903        // From the WebGL spec:
4904        //
4905        //     "If pixels is non-null but its size is less than what
4906        //      is required by the specified width, height, format,
4907        //      type, and pixel storage parameters, generates an
4908        //      INVALID_OPERATION error."
4909        if buff.len() < expected_byte_length as usize {
4910            return {
4911                self.webgl_error(InvalidOperation);
4912                Ok(())
4913            };
4914        }
4915
4916        let size = Size2D::new(width, height);
4917
4918        if !self.validate_filterable_texture(
4919            &texture,
4920            target,
4921            level,
4922            internal_format,
4923            size,
4924            data_type,
4925        ) {
4926            // FIXME(nox): What is the spec for this? No error is emitted ever
4927            // by validate_filterable_texture.
4928            return Ok(());
4929        }
4930
4931        let size = Size2D::new(width, height);
4932
4933        let (alpha_treatment, y_axis_treatment) =
4934            self.get_current_unpack_state(Alpha::NotPremultiplied);
4935
4936        self.tex_image_2d(
4937            &texture,
4938            target,
4939            data_type,
4940            internal_format,
4941            format,
4942            level,
4943            border,
4944            unpacking_alignment,
4945            size,
4946            TexSource::Pixels(TexPixels::from_array(
4947                buff,
4948                size,
4949                alpha_treatment,
4950                y_axis_treatment,
4951            )),
4952        );
4953
4954        Ok(())
4955    }
4956
4957    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
4958    fn TexImage2D_(
4959        &self,
4960        no_gc: &NoGC,
4961        target: u32,
4962        level: i32,
4963        internal_format: i32,
4964        format: u32,
4965        data_type: u32,
4966        source: TexImageSource,
4967    ) -> ErrorResult {
4968        if !self.extension_manager.is_tex_type_enabled(data_type) {
4969            self.webgl_error(InvalidEnum);
4970            return Ok(());
4971        }
4972
4973        let pixels = match self.get_image_pixels(no_gc, source)? {
4974            Some(pixels) => pixels,
4975            None => return Ok(()),
4976        };
4977
4978        let validator = TexImage2DValidator::new(
4979            self,
4980            target,
4981            level,
4982            internal_format as u32,
4983            pixels.size().width as i32,
4984            pixels.size().height as i32,
4985            0,
4986            format,
4987            data_type,
4988        );
4989
4990        let TexImage2DValidatorResult {
4991            texture,
4992            target,
4993            level,
4994            border,
4995            internal_format,
4996            format,
4997            data_type,
4998            ..
4999        } = match validator.validate() {
5000            Ok(result) => result,
5001            Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
5002        };
5003
5004        if !internal_format.compatible_data_types().contains(&data_type) {
5005            return {
5006                self.webgl_error(InvalidOperation);
5007                Ok(())
5008            };
5009        }
5010        if texture.is_immutable() {
5011            return {
5012                self.webgl_error(InvalidOperation);
5013                Ok(())
5014            };
5015        }
5016
5017        if !self.validate_filterable_texture(
5018            &texture,
5019            target,
5020            level,
5021            internal_format,
5022            pixels.size(),
5023            data_type,
5024        ) {
5025            // FIXME(nox): What is the spec for this? No error is emitted ever
5026            // by validate_filterable_texture.
5027            return Ok(());
5028        }
5029
5030        self.tex_image_2d(
5031            &texture,
5032            target,
5033            data_type,
5034            internal_format,
5035            format,
5036            level,
5037            border,
5038            1,
5039            pixels.size(),
5040            TexSource::Pixels(pixels),
5041        );
5042        Ok(())
5043    }
5044
5045    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
5046    fn TexSubImage2D(
5047        &self,
5048        no_gc: &NoGC,
5049        target: u32,
5050        level: i32,
5051        xoffset: i32,
5052        yoffset: i32,
5053        width: i32,
5054        height: i32,
5055        format: u32,
5056        data_type: u32,
5057        pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>,
5058    ) -> ErrorResult {
5059        let validator = TexImage2DValidator::new(
5060            self, target, level, format, width, height, 0, format, data_type,
5061        );
5062        let TexImage2DValidatorResult {
5063            texture,
5064            target,
5065            width,
5066            height,
5067            level,
5068            format,
5069            data_type,
5070            ..
5071        } = match validator.validate() {
5072            Ok(result) => result,
5073            Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
5074        };
5075
5076        let unpacking_alignment = self.texture_unpacking_alignment.get();
5077
5078        let expected_byte_length = match self.validate_tex_image_2d_data(
5079            width,
5080            height,
5081            format,
5082            data_type,
5083            unpacking_alignment,
5084            pixels.as_ref(),
5085        ) {
5086            Ok(byte_length) => byte_length,
5087            Err(()) => return Ok(()),
5088        };
5089
5090        let Some(data) = pixels
5091            .as_ref()
5092            .map(|p| p.as_slice_safe(no_gc).unwrap_or(&[]))
5093        else {
5094            self.webgl_error(InvalidValue);
5095            return Ok(());
5096        };
5097        let block = match self.unpack_block(
5098            data,
5099            width,
5100            height,
5101            None,
5102            bytes_per_pixel(format, data_type),
5103        ) {
5104            Ok(block) => block,
5105            Err(()) => return Ok(()),
5106        };
5107        let buff = GenericSharedMemory::from_bytes(&block);
5108
5109        // From the WebGL spec:
5110        //
5111        //     "If pixels is non-null but its size is less than what
5112        //      is required by the specified width, height, format,
5113        //      type, and pixel storage parameters, generates an
5114        //      INVALID_OPERATION error."
5115        if buff.len() < expected_byte_length as usize {
5116            return {
5117                self.webgl_error(InvalidOperation);
5118                Ok(())
5119            };
5120        }
5121
5122        let (alpha_treatment, y_axis_treatment) =
5123            self.get_current_unpack_state(Alpha::NotPremultiplied);
5124
5125        self.tex_sub_image_2d(
5126            texture,
5127            target,
5128            level,
5129            xoffset,
5130            yoffset,
5131            format,
5132            data_type,
5133            unpacking_alignment,
5134            TexPixels::from_array(
5135                buff,
5136                Size2D::new(width, height),
5137                alpha_treatment,
5138                y_axis_treatment,
5139            ),
5140        );
5141        Ok(())
5142    }
5143
5144    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
5145    fn TexSubImage2D_(
5146        &self,
5147        no_gc: &NoGC,
5148        target: u32,
5149        level: i32,
5150        xoffset: i32,
5151        yoffset: i32,
5152        format: u32,
5153        data_type: u32,
5154        source: TexImageSource,
5155    ) -> ErrorResult {
5156        let pixels = match self.get_image_pixels(no_gc, source)? {
5157            Some(pixels) => pixels,
5158            None => return Ok(()),
5159        };
5160
5161        let validator = TexImage2DValidator::new(
5162            self,
5163            target,
5164            level,
5165            format,
5166            pixels.size().width as i32,
5167            pixels.size().height as i32,
5168            0,
5169            format,
5170            data_type,
5171        );
5172        let TexImage2DValidatorResult {
5173            texture,
5174            target,
5175            level,
5176            format,
5177            data_type,
5178            ..
5179        } = match validator.validate() {
5180            Ok(result) => result,
5181            Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
5182        };
5183
5184        self.tex_sub_image_2d(
5185            texture, target, level, xoffset, yoffset, format, data_type, 1, pixels,
5186        );
5187        Ok(())
5188    }
5189
5190    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
5191    fn TexParameterf(&self, target: u32, name: u32, value: f32) {
5192        self.tex_parameter(target, name, TexParameterValue::Float(value))
5193    }
5194
5195    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
5196    fn TexParameteri(&self, target: u32, name: u32, value: i32) {
5197        self.tex_parameter(target, name, TexParameterValue::Int(value))
5198    }
5199
5200    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
5201    fn CheckFramebufferStatus(&self, target: u32) -> u32 {
5202        // From the GLES 2.0.25 spec, 4.4 ("Framebuffer Objects"):
5203        //
5204        //    "If target is not FRAMEBUFFER, INVALID_ENUM is
5205        //     generated. If CheckFramebufferStatus generates an
5206        //     error, 0 is returned."
5207        if target != constants::FRAMEBUFFER {
5208            self.webgl_error(InvalidEnum);
5209            return 0;
5210        }
5211
5212        match self.bound_draw_framebuffer.get() {
5213            Some(fb) => fb.check_status(),
5214            None => constants::FRAMEBUFFER_COMPLETE,
5215        }
5216    }
5217
5218    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
5219    fn RenderbufferStorage(&self, target: u32, internal_format: u32, width: i32, height: i32) {
5220        self.renderbuffer_storage(target, 0, internal_format, width, height)
5221    }
5222
5223    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
5224    fn FramebufferRenderbuffer(
5225        &self,
5226        target: u32,
5227        attachment: u32,
5228        renderbuffertarget: u32,
5229        rb: Option<&WebGLRenderbuffer>,
5230    ) {
5231        if let Some(rb) = rb &&
5232            let Err(error) = self.validate_ownership(rb)
5233        {
5234            return self.webgl_error(error);
5235        }
5236
5237        if target != constants::FRAMEBUFFER || renderbuffertarget != constants::RENDERBUFFER {
5238            return self.webgl_error(InvalidEnum);
5239        }
5240
5241        match self.bound_draw_framebuffer.get() {
5242            Some(fb) => {
5243                if let Err(error) = fb.renderbuffer(attachment, rb) {
5244                    self.webgl_error(error);
5245                }
5246            },
5247            None => self.webgl_error(InvalidOperation),
5248        };
5249    }
5250
5251    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6>
5252    fn FramebufferTexture2D(
5253        &self,
5254        target: u32,
5255        attachment: u32,
5256        textarget: u32,
5257        texture: Option<&WebGLTexture>,
5258        level: i32,
5259    ) {
5260        if let Some(texture) = texture &&
5261            let Err(error) = self.validate_ownership(texture)
5262        {
5263            return self.webgl_error(error);
5264        }
5265
5266        if target != constants::FRAMEBUFFER {
5267            return self.webgl_error(InvalidEnum);
5268        }
5269
5270        // From the GLES 2.0.25 spec, page 113:
5271        //
5272        //     "level specifies the mipmap level of the texture image
5273        //      to be attached to the framebuffer and must be
5274        //      0. Otherwise, INVALID_VALUE is generated."
5275        if level != 0 {
5276            return self.webgl_error(InvalidValue);
5277        }
5278
5279        match self.bound_draw_framebuffer.get() {
5280            Some(fb) => {
5281                if let Err(error) = fb.texture2d(attachment, textarget, texture, level) {
5282                    self.webgl_error(error);
5283                }
5284            },
5285            None => self.webgl_error(InvalidOperation),
5286        };
5287    }
5288
5289    /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
5290    fn GetAttachedShaders(&self, program: &WebGLProgram) -> Option<Vec<DomRoot<WebGLShader>>> {
5291        if let Err(error) = self.validate_ownership(program) {
5292            self.webgl_error(error);
5293            return None;
5294        }
5295        match program.attached_shaders() {
5296            Ok(shaders) => Some(shaders),
5297            Err(error) => {
5298                self.webgl_error(error);
5299                None
5300            },
5301        }
5302    }
5303
5304    /// <https://immersive-web.github.io/webxr/#dom-webglrenderingcontextbase-makexrcompatible>
5305    #[cfg(feature = "webxr")]
5306    fn MakeXRCompatible(&self, cx: &mut js::context::JSContext) -> RootedPromise {
5307        // XXXManishearth Fill in with compatibility checks when rust-webxr supports this
5308        Promise::new_resolved(cx, &self.global(), ())
5309    }
5310}
5311
5312#[derive(Default, JSTraceable, MallocSizeOf)]
5313struct Capabilities {
5314    value: Cell<CapFlags>,
5315}
5316
5317impl Capabilities {
5318    fn set(&self, cap: u32, set: bool) -> WebGLResult<bool> {
5319        let cap = CapFlags::from_enum(cap)?;
5320        let mut value = self.value.get();
5321        if value.contains(cap) == set {
5322            return Ok(false);
5323        }
5324        value.set(cap, set);
5325        self.value.set(value);
5326        Ok(true)
5327    }
5328
5329    fn is_enabled(&self, cap: u32) -> WebGLResult<bool> {
5330        Ok(self.value.get().contains(CapFlags::from_enum(cap)?))
5331    }
5332}
5333
5334impl Default for CapFlags {
5335    fn default() -> Self {
5336        CapFlags::DITHER
5337    }
5338}
5339
5340macro_rules! capabilities {
5341    ($name:ident, $next:ident, $($rest:ident,)*) => {
5342        capabilities!($name, $next, $($rest,)* [$name = 1;]);
5343    };
5344    ($prev:ident, $name:ident, $($rest:ident,)* [$($tt:tt)*]) => {
5345        capabilities!($name, $($rest,)* [$($tt)* $name = Self::$prev.bits() << 1;]);
5346    };
5347    ($prev:ident, [$($name:ident = $value:expr;)*]) => {
5348        #[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
5349        pub(crate) struct CapFlags(u16);
5350
5351        bitflags! {
5352            impl CapFlags: u16 {
5353                $(const $name = $value;)*
5354            }
5355        }
5356
5357        impl CapFlags {
5358            fn from_enum(cap: u32) -> WebGLResult<Self> {
5359                match cap {
5360                    $(constants::$name => Ok(Self::$name),)*
5361                    _ => Err(InvalidEnum),
5362                }
5363            }
5364        }
5365    };
5366}
5367
5368capabilities! {
5369    BLEND,
5370    CULL_FACE,
5371    DEPTH_TEST,
5372    DITHER,
5373    POLYGON_OFFSET_FILL,
5374    SAMPLE_ALPHA_TO_COVERAGE,
5375    SAMPLE_COVERAGE,
5376    SCISSOR_TEST,
5377    STENCIL_TEST,
5378}
5379
5380#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
5381#[derive(JSTraceable, MallocSizeOf)]
5382pub(crate) struct Textures {
5383    active_unit: Cell<u32>,
5384    units: Box<[TextureUnit]>,
5385}
5386
5387impl Textures {
5388    fn new(max_combined_textures: u32) -> Self {
5389        Self {
5390            active_unit: Default::default(),
5391            units: (0..max_combined_textures)
5392                .map(|_| Default::default())
5393                .collect::<Vec<_>>()
5394                .into(),
5395        }
5396    }
5397
5398    pub(crate) fn active_unit_enum(&self) -> u32 {
5399        self.active_unit.get() + constants::TEXTURE0
5400    }
5401
5402    fn set_active_unit_enum(&self, index: u32) -> WebGLResult<()> {
5403        if (constants::TEXTURE0..constants::TEXTURE0 + self.units.len() as u32).contains(&index) {
5404            self.active_unit.set(index - constants::TEXTURE0);
5405            Ok(())
5406        } else {
5407            Err(InvalidEnum)
5408        }
5409    }
5410
5411    pub(crate) fn active_texture_slot(
5412        &self,
5413        target: u32,
5414        webgl_version: WebGLVersion,
5415    ) -> WebGLResult<&MutNullableDom<WebGLTexture>> {
5416        let active_unit = self.active_unit();
5417        let is_webgl2 = webgl_version == WebGLVersion::WebGL2;
5418        match target {
5419            constants::TEXTURE_2D => Ok(&active_unit.tex_2d),
5420            constants::TEXTURE_CUBE_MAP => Ok(&active_unit.tex_cube_map),
5421            WebGL2RenderingContextConstants::TEXTURE_2D_ARRAY if is_webgl2 => {
5422                Ok(&active_unit.tex_2d_array)
5423            },
5424            WebGL2RenderingContextConstants::TEXTURE_3D if is_webgl2 => Ok(&active_unit.tex_3d),
5425            _ => Err(InvalidEnum),
5426        }
5427    }
5428
5429    pub(crate) fn active_texture_for_image_target(
5430        &self,
5431        target: TexImageTarget,
5432    ) -> Option<DomRoot<WebGLTexture>> {
5433        let active_unit = self.active_unit();
5434        match target {
5435            TexImageTarget::Texture2D => active_unit.tex_2d.get(),
5436            TexImageTarget::Texture2DArray => active_unit.tex_2d_array.get(),
5437            TexImageTarget::Texture3D => active_unit.tex_3d.get(),
5438            TexImageTarget::CubeMap |
5439            TexImageTarget::CubeMapPositiveX |
5440            TexImageTarget::CubeMapNegativeX |
5441            TexImageTarget::CubeMapPositiveY |
5442            TexImageTarget::CubeMapNegativeY |
5443            TexImageTarget::CubeMapPositiveZ |
5444            TexImageTarget::CubeMapNegativeZ => active_unit.tex_cube_map.get(),
5445        }
5446    }
5447
5448    fn active_unit(&self) -> &TextureUnit {
5449        &self.units[self.active_unit.get() as usize]
5450    }
5451
5452    fn iter(&self) -> impl Iterator<Item = (u32, &TextureUnit)> {
5453        self.units
5454            .iter()
5455            .enumerate()
5456            .map(|(index, unit)| (index as u32 + constants::TEXTURE0, unit))
5457    }
5458}
5459
5460#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
5461#[derive(Default, JSTraceable, MallocSizeOf)]
5462struct TextureUnit {
5463    tex_2d: MutNullableDom<WebGLTexture>,
5464    tex_cube_map: MutNullableDom<WebGLTexture>,
5465    tex_2d_array: MutNullableDom<WebGLTexture>,
5466    tex_3d: MutNullableDom<WebGLTexture>,
5467}
5468
5469impl TextureUnit {
5470    fn unbind(&self, texture: &WebGLTexture) -> Option<u32> {
5471        let fields = [
5472            (&self.tex_2d, constants::TEXTURE_2D),
5473            (&self.tex_cube_map, constants::TEXTURE_CUBE_MAP),
5474            (
5475                &self.tex_2d_array,
5476                WebGL2RenderingContextConstants::TEXTURE_2D_ARRAY,
5477            ),
5478            (&self.tex_3d, WebGL2RenderingContextConstants::TEXTURE_3D),
5479        ];
5480        for &(slot, target) in &fields {
5481            if slot.get().is_some_and(|t| texture == &*t) {
5482                slot.set(None);
5483                return Some(target);
5484            }
5485        }
5486        None
5487    }
5488}
5489
5490pub(crate) struct TexPixels {
5491    data: GenericSharedMemory,
5492    size: Size2D<u32>,
5493    pixel_format: Option<PixelFormat>,
5494    alpha_treatment: Option<AlphaTreatment>,
5495    y_axis_treatment: YAxisTreatment,
5496}
5497
5498impl TexPixels {
5499    fn new(
5500        data: GenericSharedMemory,
5501        size: Size2D<u32>,
5502        pixel_format: PixelFormat,
5503        alpha_treatment: Option<AlphaTreatment>,
5504        y_axis_treatment: YAxisTreatment,
5505    ) -> Self {
5506        Self {
5507            data,
5508            size,
5509            pixel_format: Some(pixel_format),
5510            alpha_treatment,
5511            y_axis_treatment,
5512        }
5513    }
5514
5515    pub(crate) fn from_array(
5516        data: GenericSharedMemory,
5517        size: Size2D<u32>,
5518        alpha_treatment: Option<AlphaTreatment>,
5519        y_axis_treatment: YAxisTreatment,
5520    ) -> Self {
5521        Self {
5522            data,
5523            size,
5524            pixel_format: None,
5525            alpha_treatment,
5526            y_axis_treatment,
5527        }
5528    }
5529
5530    pub(crate) fn size(&self) -> Size2D<u32> {
5531        self.size
5532    }
5533
5534    pub(crate) fn pixel_format(&self) -> Option<PixelFormat> {
5535        self.pixel_format
5536    }
5537
5538    pub(crate) fn alpha_treatment(&self) -> Option<AlphaTreatment> {
5539        self.alpha_treatment
5540    }
5541
5542    pub(crate) fn y_axis_treatment(&self) -> YAxisTreatment {
5543        self.y_axis_treatment
5544    }
5545
5546    pub(crate) fn into_shared_memory(self) -> GenericSharedMemory {
5547        self.data
5548    }
5549
5550    /// The `size` block at `origin`, or `None` if it does not fit. `origin` is counted in the
5551    /// image as uploaded, so from the bottom when the rows are flipped.
5552    pub(crate) fn sub_rect(self, origin: Point2D<u32>, size: Size2D<u32>) -> Option<Self> {
5553        if origin.x.checked_add(size.width)? > self.size.width ||
5554            origin.y.checked_add(size.height)? > self.size.height
5555        {
5556            return None;
5557        }
5558        if origin == Point2D::zero() && size == self.size {
5559            return Some(self);
5560        }
5561        let image_width = self.size.width as usize;
5562        let pixel_count = image_width * self.size.height as usize;
5563        let bytes_per_pixel = self.data.len().checked_div(pixel_count).unwrap_or_default();
5564        let top = match self.y_axis_treatment {
5565            YAxisTreatment::AsIs => origin.y,
5566            YAxisTreatment::Flipped => self.size.height - origin.y - size.height,
5567        } as usize;
5568        let row_bytes = size.width as usize * bytes_per_pixel;
5569        let mut block = Vec::with_capacity(row_bytes * size.height as usize);
5570        for row in top..top + size.height as usize {
5571            let start = (row * image_width + origin.x as usize) * bytes_per_pixel;
5572            block.extend_from_slice(&self.data[start..start + row_bytes]);
5573        }
5574        Some(Self {
5575            data: GenericSharedMemory::from_vec(block),
5576            size,
5577            ..self
5578        })
5579    }
5580}
5581
5582pub(crate) enum TexSource {
5583    Pixels(TexPixels),
5584    BufferOffset(i64),
5585}
5586
5587fn array_buffer_type_to_sized_type(type_: Type) -> Option<SizedDataType> {
5588    match type_ {
5589        Type::Uint8 | Type::Uint8Clamped => Some(SizedDataType::Uint8),
5590        Type::Uint16 => Some(SizedDataType::Uint16),
5591        Type::Uint32 => Some(SizedDataType::Uint32),
5592        Type::Int8 => Some(SizedDataType::Int8),
5593        Type::Int16 => Some(SizedDataType::Int16),
5594        Type::Int32 => Some(SizedDataType::Int32),
5595        Type::Float32 => Some(SizedDataType::Float32),
5596        Type::Float16 |
5597        Type::Float64 |
5598        Type::BigInt64 |
5599        Type::BigUint64 |
5600        Type::MaxTypedArrayViewType |
5601        Type::Int64 |
5602        Type::Simd128 => None,
5603    }
5604}
5605
5606/// The bytes one pixel of `format` and `data_type` takes in client memory.
5607pub(crate) fn bytes_per_pixel(format: TexFormat, data_type: TexDataType) -> u32 {
5608    data_type.element_size() * format.components() / data_type.components_per_element()
5609}
5610
5611/// Checks that `copyTexImage2D` may fill a texture of `destination` format from a color buffer
5612/// of `source` format (OpenGL ES 3.0.6 section 3.8.5); `InvalidOperation` otherwise. Returns the
5613/// type whose table 3.12 row is the new image's effective format, `None` for a sized destination.
5614fn validate_copy_tex_image_formats(
5615    source: TexFormat,
5616    destination: TexFormat,
5617) -> WebGLResult<Option<TexDataType>> {
5618    // Table 3.16: the texture may drop components but not add them; luminance reads red.
5619    let components = |format: TexFormat| -> Option<(u8, bool)> {
5620        Some(match format.to_unsized() {
5621            TexFormat::Alpha => (0b0001, false),
5622            TexFormat::Luminance | TexFormat::Red => (0b1000, false),
5623            TexFormat::LuminanceAlpha => (0b1001, false),
5624            TexFormat::RG => (0b1100, false),
5625            TexFormat::RGB => (0b1110, false),
5626            TexFormat::RGBA => (0b1111, false),
5627            TexFormat::RedInteger => (0b1000, true),
5628            TexFormat::RGInteger => (0b1100, true),
5629            TexFormat::RGBInteger => (0b1110, true),
5630            TexFormat::RGBAInteger => (0b1111, true),
5631            _ => return None,
5632        })
5633    };
5634    let (source_components, source_integer) = components(source).ok_or(InvalidOperation)?;
5635    let (destination_components, destination_integer) =
5636        components(destination).ok_or(InvalidOperation)?;
5637    if destination_components & !source_components != 0 ||
5638        source_integer != destination_integer ||
5639        source.is_srgb() != destination.is_srgb()
5640    {
5641        return Err(InvalidOperation);
5642    }
5643    let source_sizes = source.component_sizes().ok_or(InvalidOperation)?;
5644    // A sized destination must store its components exactly as the source does.
5645    if let Some(destination_sizes) = destination.component_sizes() {
5646        if destination.component_type() != source.component_type() ||
5647            destination_sizes
5648                .iter()
5649                .zip(source_sizes)
5650                .any(|(destination, source)| *destination != 0 && *destination != source)
5651        {
5652            return Err(InvalidOperation);
5653        }
5654        return Ok(None);
5655    }
5656    // An unsized destination uses the effective format with matching source sizes, if available;
5657    // otherwise, the closest larger sizes, falling back to the closest smaller ones. Float data stays float.
5658    let candidates: &[TexDataType] = match (source.component_type(), destination) {
5659        (Some(TexComponentType::Float), _) => &[TexDataType::HalfFloat, TexDataType::Float],
5660        (Some(TexComponentType::UnsignedNormalized), TexFormat::RGBA) => &[
5661            TexDataType::UnsignedByte,
5662            TexDataType::UnsignedShort4444,
5663            TexDataType::UnsignedShort5551,
5664        ],
5665        (Some(TexComponentType::UnsignedNormalized), TexFormat::RGB) => {
5666            &[TexDataType::UnsignedByte, TexDataType::UnsignedShort565]
5667        },
5668        (Some(TexComponentType::UnsignedNormalized), _) => {
5669            return Ok(Some(TexDataType::UnsignedByte));
5670        },
5671        _ => return Err(InvalidOperation),
5672    };
5673    let closest = |rule: fn(u8, u8) -> bool| {
5674        candidates
5675            .iter()
5676            .filter_map(|&data_type| {
5677                let sizes = destination
5678                    .effective_internal_format(data_type)
5679                    .component_sizes()
5680                    .expect("every candidate maps to a sized format");
5681                let distance = sizes
5682                    .iter()
5683                    .zip(source_sizes)
5684                    .filter(|(size, _)| **size != 0)
5685                    .map(|(size, source)| {
5686                        rule(*size, source).then_some(u32::from(size.abs_diff(source)))
5687                    })
5688                    .sum::<Option<u32>>()?;
5689                Some((distance, data_type))
5690            })
5691            .min_by_key(|(distance, _)| *distance)
5692            .map(|(_, data_type)| data_type)
5693    };
5694    closest(|size, source| size == source)
5695        .or_else(|| closest(|size, source| size >= source))
5696        .or_else(|| closest(|size, source| size <= source))
5697        .map(Some)
5698        .ok_or(InvalidOperation)
5699}