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