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