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