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