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    /// 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 = HashSet::new();
184            let mut used_names = HashSet::new();
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_str("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_str("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_str("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_str("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.iter().map(validate_glsl_name).collect::<Vec<_>>();
503        let first_validation_error = validation_errors.iter().find(|result| result.is_err());
504        if let Some(error) = first_validation_error {
505            return Err(error.unwrap_err());
506        }
507
508        let names = names
509            .iter()
510            .map(|name| name.to_string())
511            .collect::<Vec<_>>();
512
513        let (sender, receiver) = webgl_channel().unwrap();
514        self.upcast::<WebGLObject>()
515            .context()
516            .send_command(WebGLCommand::GetUniformIndices(self.id, names, sender));
517        Ok(receiver.recv().unwrap())
518    }
519
520    pub(crate) fn get_active_uniforms(
521        &self,
522        indices: Vec<u32>,
523        pname: u32,
524    ) -> WebGLResult<Vec<i32>> {
525        if !self.is_linked() || self.is_deleted() {
526            return Err(WebGLError::InvalidOperation);
527        }
528
529        match pname {
530            constants2::UNIFORM_TYPE |
531            constants2::UNIFORM_SIZE |
532            constants2::UNIFORM_BLOCK_INDEX |
533            constants2::UNIFORM_OFFSET |
534            constants2::UNIFORM_ARRAY_STRIDE |
535            constants2::UNIFORM_MATRIX_STRIDE |
536            constants2::UNIFORM_IS_ROW_MAJOR => {},
537            _ => return Err(WebGLError::InvalidEnum),
538        }
539
540        if indices.len() > self.active_uniforms.borrow().len() {
541            return Err(WebGLError::InvalidValue);
542        }
543
544        let (sender, receiver) = webgl_channel().unwrap();
545        self.upcast::<WebGLObject>()
546            .context()
547            .send_command(WebGLCommand::GetActiveUniforms(
548                self.id, indices, pname, sender,
549            ));
550        Ok(receiver.recv().unwrap())
551    }
552
553    pub(crate) fn get_active_uniform_block_parameter(
554        &self,
555        block_index: u32,
556        pname: u32,
557    ) -> WebGLResult<Vec<i32>> {
558        if !self.link_called.get() || self.is_deleted() {
559            return Err(WebGLError::InvalidOperation);
560        }
561
562        if block_index as usize >= self.active_uniform_blocks.borrow().len() {
563            return Err(WebGLError::InvalidValue);
564        }
565
566        match pname {
567            constants2::UNIFORM_BLOCK_BINDING |
568            constants2::UNIFORM_BLOCK_DATA_SIZE |
569            constants2::UNIFORM_BLOCK_ACTIVE_UNIFORMS |
570            constants2::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES |
571            constants2::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER |
572            constants2::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => {},
573            _ => return Err(WebGLError::InvalidEnum),
574        }
575
576        let (sender, receiver) = webgl_channel().unwrap();
577        self.upcast::<WebGLObject>().context().send_command(
578            WebGLCommand::GetActiveUniformBlockParameter(self.id, block_index, pname, sender),
579        );
580        Ok(receiver.recv().unwrap())
581    }
582
583    pub(crate) fn get_active_uniform_block_name(&self, block_index: u32) -> WebGLResult<String> {
584        if !self.link_called.get() || self.is_deleted() {
585            return Err(WebGLError::InvalidOperation);
586        }
587
588        if block_index as usize >= self.active_uniform_blocks.borrow().len() {
589            return Err(WebGLError::InvalidValue);
590        }
591
592        let (sender, receiver) = webgl_channel().unwrap();
593        self.upcast::<WebGLObject>().context().send_command(
594            WebGLCommand::GetActiveUniformBlockName(self.id, block_index, sender),
595        );
596        Ok(receiver.recv().unwrap())
597    }
598
599    pub(crate) fn bind_uniform_block(
600        &self,
601        block_index: u32,
602        block_binding: u32,
603    ) -> WebGLResult<()> {
604        if block_index as usize >= self.active_uniform_blocks.borrow().len() {
605            return Err(WebGLError::InvalidValue);
606        }
607
608        let mut active_uniforms = self.active_uniforms.borrow_mut();
609        if active_uniforms.len() > block_binding as usize {
610            active_uniforms[block_binding as usize].bind_index = Some(block_binding);
611        }
612
613        self.upcast::<WebGLObject>()
614            .context()
615            .send_command(WebGLCommand::UniformBlockBinding(
616                self.id,
617                block_index,
618                block_binding,
619            ));
620        Ok(())
621    }
622
623    /// glGetProgramInfoLog
624    pub(crate) fn get_info_log(&self) -> WebGLResult<String> {
625        if self.is_deleted() {
626            return Err(WebGLError::InvalidValue);
627        }
628        if self.link_called.get() {
629            let shaders_compiled = match (self.fragment_shader.get(), self.vertex_shader.get()) {
630                (Some(fs), Some(vs)) => fs.successfully_compiled() && vs.successfully_compiled(),
631                _ => false,
632            };
633            if !shaders_compiled {
634                return Ok("One or more shaders failed to compile".to_string());
635            }
636        }
637        let (sender, receiver) = webgl_channel().unwrap();
638        self.upcast::<WebGLObject>()
639            .context()
640            .send_command(WebGLCommand::GetProgramInfoLog(self.id, sender));
641        Ok(receiver.recv().unwrap())
642    }
643
644    pub(crate) fn attached_shaders(&self) -> WebGLResult<Vec<DomRoot<WebGLShader>>> {
645        if self.marked_for_deletion.get() {
646            return Err(WebGLError::InvalidValue);
647        }
648        Ok(
649            match (self.vertex_shader.get(), self.fragment_shader.get()) {
650                (Some(vertex_shader), Some(fragment_shader)) => {
651                    vec![vertex_shader, fragment_shader]
652                },
653                (Some(shader), None) | (None, Some(shader)) => vec![shader],
654                (None, None) => vec![],
655            },
656        )
657    }
658
659    pub(crate) fn link_generation(&self) -> u64 {
660        self.link_generation.get()
661    }
662
663    pub(crate) fn transform_feedback_varyings_length(&self) -> i32 {
664        self.transform_feedback_varyings_length.get()
665    }
666
667    pub(crate) fn transform_feedback_buffer_mode(&self) -> i32 {
668        self.transform_feedback_mode.get()
669    }
670}
671
672impl Drop for WebGLProgram {
673    fn drop(&mut self) {
674        self.in_use(false);
675        self.mark_for_deletion(Operation::Fallible);
676    }
677}
678
679fn validate_glsl_name(name: &DOMString) -> WebGLResult<bool> {
680    if name.is_empty() {
681        return Ok(false);
682    }
683    if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN {
684        return Err(WebGLError::InvalidValue);
685    }
686    for c in name.str().chars() {
687        validate_glsl_char(c)?;
688    }
689    if name.starts_with_str("webgl_") || name.starts_with_str("_webgl_") {
690        return Err(WebGLError::InvalidOperation);
691    }
692    Ok(true)
693}
694
695fn validate_glsl_char(c: char) -> WebGLResult<()> {
696    match c {
697        'a'..='z' |
698        'A'..='Z' |
699        '0'..='9' |
700        ' ' |
701        '\t' |
702        '\u{11}' |
703        '\u{12}' |
704        '\r' |
705        '\n' |
706        '_' |
707        '.' |
708        '+' |
709        '-' |
710        '/' |
711        '*' |
712        '%' |
713        '<' |
714        '>' |
715        '[' |
716        ']' |
717        '(' |
718        ')' |
719        '{' |
720        '}' |
721        '^' |
722        '|' |
723        '&' |
724        '~' |
725        '=' |
726        '!' |
727        ':' |
728        ';' |
729        ',' |
730        '?' => Ok(()),
731        _ => Err(WebGLError::InvalidValue),
732    }
733}
734
735fn parse_uniform_name(name: &DOMString) -> Option<(String, Option<i32>)> {
736    let name = name.str();
737    if !name.ends_with(']') {
738        return Some((String::from(name), None));
739    }
740    let bracket_pos = name[..name.len() - 1].rfind('[')?;
741    let index = name[(bracket_pos + 1)..(name.len() - 1)]
742        .parse::<i32>()
743        .ok()?;
744    Some((String::from(&name[..bracket_pos]), Some(index)))
745}
746
747pub(crate) const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256;