script/dom/webgl/
webglprogram.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
5// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
6use std::cell::Cell;
7use std::collections::HashSet;
8
9use canvas_traits::webgl::{
10    ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, WebGLCommand, WebGLError,
11    WebGLProgramId, WebGLResult, webgl_channel,
12};
13use dom_struct::dom_struct;
14
15use crate::canvas_context::CanvasContext;
16use crate::dom::bindings::cell::{DomRefCell, Ref};
17use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants2;
18use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
21use crate::dom::bindings::root::{DomRoot, MutNullableDom};
22use crate::dom::bindings::str::DOMString;
23use crate::dom::webgl::webglactiveinfo::WebGLActiveInfo;
24use crate::dom::webgl::webglobject::WebGLObject;
25use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
26use crate::dom::webgl::webglshader::WebGLShader;
27use crate::dom::webgl::webgluniformlocation::WebGLUniformLocation;
28use crate::script_runtime::CanGc;
29
30#[dom_struct]
31pub(crate) struct WebGLProgram {
32    webgl_object: WebGLObject,
33    #[no_trace]
34    id: WebGLProgramId,
35    is_in_use: Cell<bool>,
36    marked_for_deletion: Cell<bool>,
37    link_called: Cell<bool>,
38    linked: Cell<bool>,
39    link_generation: Cell<u64>,
40    fragment_shader: MutNullableDom<WebGLShader>,
41    vertex_shader: MutNullableDom<WebGLShader>,
42    #[no_trace]
43    active_attribs: DomRefCell<Box<[ActiveAttribInfo]>>,
44    #[no_trace]
45    active_uniforms: DomRefCell<Box<[ActiveUniformInfo]>>,
46    #[no_trace]
47    active_uniform_blocks: DomRefCell<Box<[ActiveUniformBlockInfo]>>,
48    transform_feedback_varyings_length: Cell<i32>,
49    transform_feedback_mode: Cell<i32>,
50}
51
52impl WebGLProgram {
53    fn new_inherited(context: &WebGLRenderingContext, id: WebGLProgramId) -> Self {
54        Self {
55            webgl_object: WebGLObject::new_inherited(context),
56            id,
57            is_in_use: Default::default(),
58            marked_for_deletion: Default::default(),
59            link_called: Default::default(),
60            linked: Default::default(),
61            link_generation: Default::default(),
62            fragment_shader: Default::default(),
63            vertex_shader: Default::default(),
64            active_attribs: DomRefCell::new(vec![].into()),
65            active_uniforms: DomRefCell::new(vec![].into()),
66            active_uniform_blocks: DomRefCell::new(vec![].into()),
67            transform_feedback_varyings_length: Default::default(),
68            transform_feedback_mode: Default::default(),
69        }
70    }
71
72    pub(crate) fn maybe_new(
73        context: &WebGLRenderingContext,
74        can_gc: CanGc,
75    ) -> Option<DomRoot<Self>> {
76        let (sender, receiver) = webgl_channel().unwrap();
77        context.send_command(WebGLCommand::CreateProgram(sender));
78        receiver
79            .recv()
80            .unwrap()
81            .map(|id| WebGLProgram::new(context, id, can_gc))
82    }
83
84    pub(crate) fn new(
85        context: &WebGLRenderingContext,
86        id: WebGLProgramId,
87        can_gc: CanGc,
88    ) -> DomRoot<Self> {
89        reflect_dom_object(
90            Box::new(WebGLProgram::new_inherited(context, id)),
91            &*context.global(),
92            can_gc,
93        )
94    }
95}
96
97impl WebGLProgram {
98    pub(crate) fn id(&self) -> WebGLProgramId {
99        self.id
100    }
101
102    pub(crate) fn context(&self) -> &WebGLRenderingContext {
103        self.upcast::<WebGLObject>().context()
104    }
105
106    /// glDeleteProgram
107    pub(crate) fn mark_for_deletion(&self, operation_fallibility: Operation) {
108        if self.marked_for_deletion.get() {
109            return;
110        }
111        self.marked_for_deletion.set(true);
112        let cmd = WebGLCommand::DeleteProgram(self.id);
113        let context = self.upcast::<WebGLObject>().context();
114        match operation_fallibility {
115            Operation::Fallible => context.send_command_ignored(cmd),
116            Operation::Infallible => context.send_command(cmd),
117        }
118        if self.is_deleted() {
119            self.detach_shaders();
120        }
121    }
122
123    pub(crate) fn in_use(&self, value: bool) {
124        if self.is_in_use.get() == value {
125            return;
126        }
127        self.is_in_use.set(value);
128        if self.is_deleted() {
129            self.detach_shaders();
130        }
131    }
132
133    fn detach_shaders(&self) {
134        assert!(self.is_deleted());
135        if let Some(shader) = self.fragment_shader.get() {
136            shader.decrement_attached_counter();
137            self.fragment_shader.set(None);
138        }
139        if let Some(shader) = self.vertex_shader.get() {
140            shader.decrement_attached_counter();
141            self.vertex_shader.set(None);
142        }
143    }
144
145    pub(crate) fn is_in_use(&self) -> bool {
146        self.is_in_use.get()
147    }
148
149    pub(crate) fn is_marked_for_deletion(&self) -> bool {
150        self.marked_for_deletion.get()
151    }
152
153    pub(crate) fn is_deleted(&self) -> bool {
154        self.marked_for_deletion.get() && !self.is_in_use.get()
155    }
156
157    pub(crate) fn is_linked(&self) -> bool {
158        self.linked.get()
159    }
160
161    /// glLinkProgram
162    pub(crate) fn link(&self) -> WebGLResult<()> {
163        self.linked.set(false);
164        self.link_generation
165            .set(self.link_generation.get().checked_add(1).unwrap());
166        *self.active_attribs.borrow_mut() = Box::new([]);
167        *self.active_uniforms.borrow_mut() = Box::new([]);
168        *self.active_uniform_blocks.borrow_mut() = Box::new([]);
169
170        match self.fragment_shader.get() {
171            Some(ref shader) if shader.successfully_compiled() => {},
172            _ => return Ok(()), // callers use gl.LINK_STATUS to check link errors
173        }
174
175        match self.vertex_shader.get() {
176            Some(ref shader) if shader.successfully_compiled() => {},
177            _ => return Ok(()), // callers use gl.LINK_STATUS to check link errors
178        }
179
180        let (sender, receiver) = webgl_channel().unwrap();
181        self.upcast::<WebGLObject>()
182            .context()
183            .send_command(WebGLCommand::LinkProgram(self.id, sender));
184        let link_info = receiver.recv().unwrap();
185
186        {
187            let mut used_locs = HashSet::new();
188            let mut used_names = HashSet::new();
189            for active_attrib in &*link_info.active_attribs {
190                let Some(location) = active_attrib.location else {
191                    continue;
192                };
193                let columns = match active_attrib.type_ {
194                    constants::FLOAT_MAT2 => 2,
195                    constants::FLOAT_MAT3 => 3,
196                    constants::FLOAT_MAT4 => 4,
197                    _ => 1,
198                };
199                assert!(used_names.insert(&*active_attrib.name));
200                for column in 0..columns {
201                    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.31
202                    if !used_locs.insert(location + column) {
203                        return Ok(());
204                    }
205                }
206            }
207            for active_uniform in &*link_info.active_uniforms {
208                // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.41
209                if !used_names.insert(&*active_uniform.base_name) {
210                    return Ok(());
211                }
212            }
213        }
214
215        self.linked.set(link_info.linked);
216        self.link_called.set(true);
217        self.transform_feedback_varyings_length
218            .set(link_info.transform_feedback_length);
219        self.transform_feedback_mode
220            .set(link_info.transform_feedback_mode);
221        *self.active_attribs.borrow_mut() = link_info.active_attribs;
222        *self.active_uniforms.borrow_mut() = link_info.active_uniforms;
223        *self.active_uniform_blocks.borrow_mut() = link_info.active_uniform_blocks;
224        Ok(())
225    }
226
227    pub(crate) fn active_attribs(&self) -> Ref<'_, [ActiveAttribInfo]> {
228        Ref::map(self.active_attribs.borrow(), |attribs| &**attribs)
229    }
230
231    pub(crate) fn active_uniforms(&self) -> Ref<'_, [ActiveUniformInfo]> {
232        Ref::map(self.active_uniforms.borrow(), |uniforms| &**uniforms)
233    }
234
235    pub(crate) fn active_uniform_blocks(&self) -> Ref<'_, [ActiveUniformBlockInfo]> {
236        Ref::map(self.active_uniform_blocks.borrow(), |blocks| &**blocks)
237    }
238
239    /// glValidateProgram
240    pub(crate) fn validate(&self) -> WebGLResult<()> {
241        if self.is_deleted() {
242            return Err(WebGLError::InvalidOperation);
243        }
244        self.upcast::<WebGLObject>()
245            .context()
246            .send_command(WebGLCommand::ValidateProgram(self.id));
247        Ok(())
248    }
249
250    /// glAttachShader
251    pub(crate) fn attach_shader(&self, shader: &WebGLShader) -> WebGLResult<()> {
252        if self.is_deleted() || shader.is_deleted() {
253            return Err(WebGLError::InvalidOperation);
254        }
255        let shader_slot = match shader.gl_type() {
256            constants::FRAGMENT_SHADER => &self.fragment_shader,
257            constants::VERTEX_SHADER => &self.vertex_shader,
258            _ => {
259                error!("detachShader: Unexpected shader type");
260                return Err(WebGLError::InvalidValue);
261            },
262        };
263
264        if shader_slot.get().is_some() {
265            return Err(WebGLError::InvalidOperation);
266        }
267
268        shader_slot.set(Some(shader));
269        shader.increment_attached_counter();
270
271        self.upcast::<WebGLObject>()
272            .context()
273            .send_command(WebGLCommand::AttachShader(self.id, shader.id()));
274
275        Ok(())
276    }
277
278    /// glDetachShader
279    pub(crate) fn detach_shader(&self, shader: &WebGLShader) -> WebGLResult<()> {
280        if self.is_deleted() {
281            return Err(WebGLError::InvalidOperation);
282        }
283        let shader_slot = match shader.gl_type() {
284            constants::FRAGMENT_SHADER => &self.fragment_shader,
285            constants::VERTEX_SHADER => &self.vertex_shader,
286            _ => return Err(WebGLError::InvalidValue),
287        };
288
289        match shader_slot.get() {
290            Some(ref attached_shader) if attached_shader.id() != shader.id() => {
291                return Err(WebGLError::InvalidOperation);
292            },
293            None => return Err(WebGLError::InvalidOperation),
294            _ => {},
295        }
296
297        shader_slot.set(None);
298        shader.decrement_attached_counter();
299
300        self.upcast::<WebGLObject>()
301            .context()
302            .send_command(WebGLCommand::DetachShader(self.id, shader.id()));
303
304        Ok(())
305    }
306
307    /// glBindAttribLocation
308    pub(crate) fn bind_attrib_location(&self, index: u32, name: DOMString) -> WebGLResult<()> {
309        if self.is_deleted() {
310            return Err(WebGLError::InvalidOperation);
311        }
312
313        if !validate_glsl_name(&name)? {
314            return Ok(());
315        }
316        if name.starts_with_str("gl_") {
317            return Err(WebGLError::InvalidOperation);
318        }
319
320        self.upcast::<WebGLObject>()
321            .context()
322            .send_command(WebGLCommand::BindAttribLocation(
323                self.id,
324                index,
325                name.into(),
326            ));
327        Ok(())
328    }
329
330    pub(crate) fn get_active_uniform(
331        &self,
332        index: u32,
333        can_gc: CanGc,
334    ) -> WebGLResult<DomRoot<WebGLActiveInfo>> {
335        if self.is_deleted() {
336            return Err(WebGLError::InvalidValue);
337        }
338        let uniforms = self.active_uniforms.borrow();
339        let data = uniforms
340            .get(index as usize)
341            .ok_or(WebGLError::InvalidValue)?;
342        Ok(WebGLActiveInfo::new(
343            self.global().as_window(),
344            data.size.unwrap_or(1),
345            data.type_,
346            data.name().into(),
347            can_gc,
348        ))
349    }
350
351    /// glGetActiveAttrib
352    pub(crate) fn get_active_attrib(
353        &self,
354        index: u32,
355        can_gc: CanGc,
356    ) -> WebGLResult<DomRoot<WebGLActiveInfo>> {
357        if self.is_deleted() {
358            return Err(WebGLError::InvalidValue);
359        }
360        let attribs = self.active_attribs.borrow();
361        let data = attribs
362            .get(index as usize)
363            .ok_or(WebGLError::InvalidValue)?;
364        Ok(WebGLActiveInfo::new(
365            self.global().as_window(),
366            data.size,
367            data.type_,
368            data.name.clone().into(),
369            can_gc,
370        ))
371    }
372
373    /// glGetAttribLocation
374    pub(crate) fn get_attrib_location(&self, name: DOMString) -> WebGLResult<i32> {
375        if !self.is_linked() || self.is_deleted() {
376            return Err(WebGLError::InvalidOperation);
377        }
378
379        if !validate_glsl_name(&name)? {
380            return Ok(-1);
381        }
382        if name.starts_with_str("gl_") {
383            return Ok(-1);
384        }
385
386        let location = self
387            .active_attribs
388            .borrow()
389            .iter()
390            .find(|attrib| *attrib.name == name)
391            .and_then(|attrib| attrib.location.map(|l| l as i32))
392            .unwrap_or(-1);
393        Ok(location)
394    }
395
396    /// glGetFragDataLocation
397    pub(crate) fn get_frag_data_location(&self, name: DOMString) -> WebGLResult<i32> {
398        if !self.is_linked() || self.is_deleted() {
399            return Err(WebGLError::InvalidOperation);
400        }
401
402        if !validate_glsl_name(&name)? {
403            return Ok(-1);
404        }
405        if name.starts_with_str("gl_") {
406            return Ok(-1);
407        }
408
409        let (sender, receiver) = webgl_channel().unwrap();
410        self.upcast::<WebGLObject>()
411            .context()
412            .send_command(WebGLCommand::GetFragDataLocation(
413                self.id,
414                name.into(),
415                sender,
416            ));
417        Ok(receiver.recv().unwrap())
418    }
419
420    /// glGetUniformLocation
421    pub(crate) fn get_uniform_location(
422        &self,
423        name: DOMString,
424        can_gc: CanGc,
425    ) -> WebGLResult<Option<DomRoot<WebGLUniformLocation>>> {
426        if !self.is_linked() || self.is_deleted() {
427            return Err(WebGLError::InvalidOperation);
428        }
429
430        if !validate_glsl_name(&name)? {
431            return Ok(None);
432        }
433        if name.starts_with_str("gl_") {
434            return Ok(None);
435        }
436
437        let (size, type_) = {
438            let (base_name, array_index) = match parse_uniform_name(&name) {
439                Some((name, index)) if index.is_none_or(|i| i >= 0) => (name, index),
440                _ => return Ok(None),
441            };
442
443            let uniforms = self.active_uniforms.borrow();
444            match uniforms
445                .iter()
446                .find(|attrib| *attrib.base_name == base_name)
447            {
448                Some(uniform) if array_index.is_none() || array_index < uniform.size => (
449                    uniform
450                        .size
451                        .map(|size| size - array_index.unwrap_or_default()),
452                    uniform.type_,
453                ),
454                _ => return Ok(None),
455            }
456        };
457
458        let (sender, receiver) = webgl_channel().unwrap();
459        self.upcast::<WebGLObject>()
460            .context()
461            .send_command(WebGLCommand::GetUniformLocation(
462                self.id,
463                name.into(),
464                sender,
465            ));
466        let location = receiver.recv().unwrap();
467        let context_id = self.upcast::<WebGLObject>().context().context_id();
468
469        Ok(Some(WebGLUniformLocation::new(
470            self.global().as_window(),
471            location,
472            context_id,
473            self.id,
474            self.link_generation.get(),
475            size,
476            type_,
477            can_gc,
478        )))
479    }
480
481    pub(crate) fn get_uniform_block_index(&self, name: DOMString) -> WebGLResult<u32> {
482        if !self.link_called.get() || self.is_deleted() {
483            return Err(WebGLError::InvalidOperation);
484        }
485
486        if !validate_glsl_name(&name)? {
487            return Ok(constants2::INVALID_INDEX);
488        }
489
490        let (sender, receiver) = webgl_channel().unwrap();
491        self.upcast::<WebGLObject>()
492            .context()
493            .send_command(WebGLCommand::GetUniformBlockIndex(
494                self.id,
495                name.into(),
496                sender,
497            ));
498        Ok(receiver.recv().unwrap())
499    }
500
501    pub(crate) fn get_uniform_indices(&self, names: Vec<DOMString>) -> WebGLResult<Vec<u32>> {
502        if !self.link_called.get() || self.is_deleted() {
503            return Err(WebGLError::InvalidOperation);
504        }
505
506        let validation_errors = names.iter().map(validate_glsl_name).collect::<Vec<_>>();
507        let first_validation_error = validation_errors.iter().find(|result| result.is_err());
508        if let Some(error) = first_validation_error {
509            return Err(error.unwrap_err());
510        }
511
512        let names = names
513            .iter()
514            .map(|name| name.to_string())
515            .collect::<Vec<_>>();
516
517        let (sender, receiver) = webgl_channel().unwrap();
518        self.upcast::<WebGLObject>()
519            .context()
520            .send_command(WebGLCommand::GetUniformIndices(self.id, names, sender));
521        Ok(receiver.recv().unwrap())
522    }
523
524    pub(crate) fn get_active_uniforms(
525        &self,
526        indices: Vec<u32>,
527        pname: u32,
528    ) -> WebGLResult<Vec<i32>> {
529        if !self.is_linked() || self.is_deleted() {
530            return Err(WebGLError::InvalidOperation);
531        }
532
533        match pname {
534            constants2::UNIFORM_TYPE |
535            constants2::UNIFORM_SIZE |
536            constants2::UNIFORM_BLOCK_INDEX |
537            constants2::UNIFORM_OFFSET |
538            constants2::UNIFORM_ARRAY_STRIDE |
539            constants2::UNIFORM_MATRIX_STRIDE |
540            constants2::UNIFORM_IS_ROW_MAJOR => {},
541            _ => return Err(WebGLError::InvalidEnum),
542        }
543
544        if indices.len() > self.active_uniforms.borrow().len() {
545            return Err(WebGLError::InvalidValue);
546        }
547
548        let (sender, receiver) = webgl_channel().unwrap();
549        self.upcast::<WebGLObject>()
550            .context()
551            .send_command(WebGLCommand::GetActiveUniforms(
552                self.id, indices, pname, sender,
553            ));
554        Ok(receiver.recv().unwrap())
555    }
556
557    pub(crate) fn get_active_uniform_block_parameter(
558        &self,
559        block_index: u32,
560        pname: u32,
561    ) -> WebGLResult<Vec<i32>> {
562        if !self.link_called.get() || self.is_deleted() {
563            return Err(WebGLError::InvalidOperation);
564        }
565
566        if block_index as usize >= self.active_uniform_blocks.borrow().len() {
567            return Err(WebGLError::InvalidValue);
568        }
569
570        match pname {
571            constants2::UNIFORM_BLOCK_BINDING |
572            constants2::UNIFORM_BLOCK_DATA_SIZE |
573            constants2::UNIFORM_BLOCK_ACTIVE_UNIFORMS |
574            constants2::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES |
575            constants2::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER |
576            constants2::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => {},
577            _ => return Err(WebGLError::InvalidEnum),
578        }
579
580        let (sender, receiver) = webgl_channel().unwrap();
581        self.upcast::<WebGLObject>().context().send_command(
582            WebGLCommand::GetActiveUniformBlockParameter(self.id, block_index, pname, sender),
583        );
584        Ok(receiver.recv().unwrap())
585    }
586
587    pub(crate) fn get_active_uniform_block_name(&self, block_index: u32) -> WebGLResult<String> {
588        if !self.link_called.get() || self.is_deleted() {
589            return Err(WebGLError::InvalidOperation);
590        }
591
592        if block_index as usize >= self.active_uniform_blocks.borrow().len() {
593            return Err(WebGLError::InvalidValue);
594        }
595
596        let (sender, receiver) = webgl_channel().unwrap();
597        self.upcast::<WebGLObject>().context().send_command(
598            WebGLCommand::GetActiveUniformBlockName(self.id, block_index, sender),
599        );
600        Ok(receiver.recv().unwrap())
601    }
602
603    pub(crate) fn bind_uniform_block(
604        &self,
605        block_index: u32,
606        block_binding: u32,
607    ) -> WebGLResult<()> {
608        if block_index as usize >= self.active_uniform_blocks.borrow().len() {
609            return Err(WebGLError::InvalidValue);
610        }
611
612        let mut active_uniforms = self.active_uniforms.borrow_mut();
613        if active_uniforms.len() > block_binding as usize {
614            active_uniforms[block_binding as usize].bind_index = Some(block_binding);
615        }
616
617        self.upcast::<WebGLObject>()
618            .context()
619            .send_command(WebGLCommand::UniformBlockBinding(
620                self.id,
621                block_index,
622                block_binding,
623            ));
624        Ok(())
625    }
626
627    /// glGetProgramInfoLog
628    pub(crate) fn get_info_log(&self) -> WebGLResult<String> {
629        if self.is_deleted() {
630            return Err(WebGLError::InvalidValue);
631        }
632        if self.link_called.get() {
633            let shaders_compiled = match (self.fragment_shader.get(), self.vertex_shader.get()) {
634                (Some(fs), Some(vs)) => fs.successfully_compiled() && vs.successfully_compiled(),
635                _ => false,
636            };
637            if !shaders_compiled {
638                return Ok("One or more shaders failed to compile".to_string());
639            }
640        }
641        let (sender, receiver) = webgl_channel().unwrap();
642        self.upcast::<WebGLObject>()
643            .context()
644            .send_command(WebGLCommand::GetProgramInfoLog(self.id, sender));
645        Ok(receiver.recv().unwrap())
646    }
647
648    pub(crate) fn attached_shaders(&self) -> WebGLResult<Vec<DomRoot<WebGLShader>>> {
649        if self.marked_for_deletion.get() {
650            return Err(WebGLError::InvalidValue);
651        }
652        Ok(
653            match (self.vertex_shader.get(), self.fragment_shader.get()) {
654                (Some(vertex_shader), Some(fragment_shader)) => {
655                    vec![vertex_shader, fragment_shader]
656                },
657                (Some(shader), None) | (None, Some(shader)) => vec![shader],
658                (None, None) => vec![],
659            },
660        )
661    }
662
663    pub(crate) fn link_generation(&self) -> u64 {
664        self.link_generation.get()
665    }
666
667    pub(crate) fn transform_feedback_varyings_length(&self) -> i32 {
668        self.transform_feedback_varyings_length.get()
669    }
670
671    pub(crate) fn transform_feedback_buffer_mode(&self) -> i32 {
672        self.transform_feedback_mode.get()
673    }
674}
675
676impl Drop for WebGLProgram {
677    fn drop(&mut self) {
678        self.in_use(false);
679        self.mark_for_deletion(Operation::Fallible);
680    }
681}
682
683fn validate_glsl_name(name: &DOMString) -> WebGLResult<bool> {
684    if name.is_empty() {
685        return Ok(false);
686    }
687    if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN {
688        return Err(WebGLError::InvalidValue);
689    }
690    for c in name.str().chars() {
691        validate_glsl_char(c)?;
692    }
693    if name.starts_with_str("webgl_") || name.starts_with_str("_webgl_") {
694        return Err(WebGLError::InvalidOperation);
695    }
696    Ok(true)
697}
698
699fn validate_glsl_char(c: char) -> WebGLResult<()> {
700    match c {
701        'a'..='z' |
702        'A'..='Z' |
703        '0'..='9' |
704        ' ' |
705        '\t' |
706        '\u{11}' |
707        '\u{12}' |
708        '\r' |
709        '\n' |
710        '_' |
711        '.' |
712        '+' |
713        '-' |
714        '/' |
715        '*' |
716        '%' |
717        '<' |
718        '>' |
719        '[' |
720        ']' |
721        '(' |
722        ')' |
723        '{' |
724        '}' |
725        '^' |
726        '|' |
727        '&' |
728        '~' |
729        '=' |
730        '!' |
731        ':' |
732        ';' |
733        ',' |
734        '?' => Ok(()),
735        _ => Err(WebGLError::InvalidValue),
736    }
737}
738
739fn parse_uniform_name(name: &DOMString) -> Option<(String, Option<i32>)> {
740    let name = name.str();
741    if !name.ends_with(']') {
742        return Some((String::from(name), None));
743    }
744    let bracket_pos = name[..name.len() - 1].rfind('[')?;
745    let index = name[(bracket_pos + 1)..(name.len() - 1)]
746        .parse::<i32>()
747        .ok()?;
748    Some((String::from(&name[..bracket_pos]), Some(index)))
749}
750
751pub(crate) const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256;