Files
servo/components/script/dom/webgl/webglprogram.rs
Narfinger 423800eec4 Script: Lazily transform the DOMString into Rust String instead of immediately. (#39509)
This implements LazyDOMString (from now on DOMString) as outlined in
https://github.com/servo/servo/issues/39479.
Constructing from a *mut JSString we keep the in a
RootedTraceableBox<Heap<*mut JSString>> and transform
the string into a rust string if necessary via the `make_rust_string`
method.
Methods used in script are implemented on this string. Currently we
transform the string at all times.
But in the future more efficient implementations are possible.

We implement the safety critical sections in a separate module
DOMStringInner which allows simple constructors, `make_rust_string` and
the `bytes` method.
This method returns the new type `EncodedBytes` which contains the
reference to the underlying string in either format.

Testing: WPT tests still seem to work, so this should test this
functionality.

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2025-10-09 18:18:03 +00:00

748 lines
24 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use std::cell::Cell;
use std::collections::HashSet;
use canvas_traits::webgl::{
ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, WebGLCommand, WebGLError,
WebGLProgramId, WebGLResult, webgl_channel,
};
use dom_struct::dom_struct;
use crate::canvas_context::CanvasContext;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants2;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::webgl::webglactiveinfo::WebGLActiveInfo;
use crate::dom::webgl::webglobject::WebGLObject;
use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
use crate::dom::webgl::webglshader::WebGLShader;
use crate::dom::webgl::webgluniformlocation::WebGLUniformLocation;
use crate::script_runtime::CanGc;
#[dom_struct]
pub(crate) struct WebGLProgram {
webgl_object: WebGLObject,
#[no_trace]
id: WebGLProgramId,
is_in_use: Cell<bool>,
marked_for_deletion: Cell<bool>,
link_called: Cell<bool>,
linked: Cell<bool>,
link_generation: Cell<u64>,
fragment_shader: MutNullableDom<WebGLShader>,
vertex_shader: MutNullableDom<WebGLShader>,
#[no_trace]
active_attribs: DomRefCell<Box<[ActiveAttribInfo]>>,
#[no_trace]
active_uniforms: DomRefCell<Box<[ActiveUniformInfo]>>,
#[no_trace]
active_uniform_blocks: DomRefCell<Box<[ActiveUniformBlockInfo]>>,
transform_feedback_varyings_length: Cell<i32>,
transform_feedback_mode: Cell<i32>,
}
impl WebGLProgram {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLProgramId) -> Self {
Self {
webgl_object: WebGLObject::new_inherited(context),
id,
is_in_use: Default::default(),
marked_for_deletion: Default::default(),
link_called: Default::default(),
linked: Default::default(),
link_generation: Default::default(),
fragment_shader: Default::default(),
vertex_shader: Default::default(),
active_attribs: DomRefCell::new(vec![].into()),
active_uniforms: DomRefCell::new(vec![].into()),
active_uniform_blocks: DomRefCell::new(vec![].into()),
transform_feedback_varyings_length: Default::default(),
transform_feedback_mode: Default::default(),
}
}
pub(crate) fn maybe_new(
context: &WebGLRenderingContext,
can_gc: CanGc,
) -> Option<DomRoot<Self>> {
let (sender, receiver) = webgl_channel().unwrap();
context.send_command(WebGLCommand::CreateProgram(sender));
receiver
.recv()
.unwrap()
.map(|id| WebGLProgram::new(context, id, can_gc))
}
pub(crate) fn new(
context: &WebGLRenderingContext,
id: WebGLProgramId,
can_gc: CanGc,
) -> DomRoot<Self> {
reflect_dom_object(
Box::new(WebGLProgram::new_inherited(context, id)),
&*context.global(),
can_gc,
)
}
}
impl WebGLProgram {
pub(crate) fn id(&self) -> WebGLProgramId {
self.id
}
/// glDeleteProgram
pub(crate) fn mark_for_deletion(&self, operation_fallibility: Operation) {
if self.marked_for_deletion.get() {
return;
}
self.marked_for_deletion.set(true);
let cmd = WebGLCommand::DeleteProgram(self.id);
let context = self.upcast::<WebGLObject>().context();
match operation_fallibility {
Operation::Fallible => context.send_command_ignored(cmd),
Operation::Infallible => context.send_command(cmd),
}
if self.is_deleted() {
self.detach_shaders();
}
}
pub(crate) fn in_use(&self, value: bool) {
if self.is_in_use.get() == value {
return;
}
self.is_in_use.set(value);
if self.is_deleted() {
self.detach_shaders();
}
}
fn detach_shaders(&self) {
assert!(self.is_deleted());
if let Some(shader) = self.fragment_shader.get() {
shader.decrement_attached_counter();
self.fragment_shader.set(None);
}
if let Some(shader) = self.vertex_shader.get() {
shader.decrement_attached_counter();
self.vertex_shader.set(None);
}
}
pub(crate) fn is_in_use(&self) -> bool {
self.is_in_use.get()
}
pub(crate) fn is_marked_for_deletion(&self) -> bool {
self.marked_for_deletion.get()
}
pub(crate) fn is_deleted(&self) -> bool {
self.marked_for_deletion.get() && !self.is_in_use.get()
}
pub(crate) fn is_linked(&self) -> bool {
self.linked.get()
}
/// glLinkProgram
pub(crate) fn link(&self) -> WebGLResult<()> {
self.linked.set(false);
self.link_generation
.set(self.link_generation.get().checked_add(1).unwrap());
*self.active_attribs.borrow_mut() = Box::new([]);
*self.active_uniforms.borrow_mut() = Box::new([]);
*self.active_uniform_blocks.borrow_mut() = Box::new([]);
match self.fragment_shader.get() {
Some(ref shader) if shader.successfully_compiled() => {},
_ => return Ok(()), // callers use gl.LINK_STATUS to check link errors
}
match self.vertex_shader.get() {
Some(ref shader) if shader.successfully_compiled() => {},
_ => return Ok(()), // callers use gl.LINK_STATUS to check link errors
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::LinkProgram(self.id, sender));
let link_info = receiver.recv().unwrap();
{
let mut used_locs = HashSet::new();
let mut used_names = HashSet::new();
for active_attrib in &*link_info.active_attribs {
let Some(location) = active_attrib.location else {
continue;
};
let columns = match active_attrib.type_ {
constants::FLOAT_MAT2 => 2,
constants::FLOAT_MAT3 => 3,
constants::FLOAT_MAT4 => 4,
_ => 1,
};
assert!(used_names.insert(&*active_attrib.name));
for column in 0..columns {
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.31
if !used_locs.insert(location + column) {
return Ok(());
}
}
}
for active_uniform in &*link_info.active_uniforms {
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.41
if !used_names.insert(&*active_uniform.base_name) {
return Ok(());
}
}
}
self.linked.set(link_info.linked);
self.link_called.set(true);
self.transform_feedback_varyings_length
.set(link_info.transform_feedback_length);
self.transform_feedback_mode
.set(link_info.transform_feedback_mode);
*self.active_attribs.borrow_mut() = link_info.active_attribs;
*self.active_uniforms.borrow_mut() = link_info.active_uniforms;
*self.active_uniform_blocks.borrow_mut() = link_info.active_uniform_blocks;
Ok(())
}
pub(crate) fn active_attribs(&self) -> Ref<'_, [ActiveAttribInfo]> {
Ref::map(self.active_attribs.borrow(), |attribs| &**attribs)
}
pub(crate) fn active_uniforms(&self) -> Ref<'_, [ActiveUniformInfo]> {
Ref::map(self.active_uniforms.borrow(), |uniforms| &**uniforms)
}
pub(crate) fn active_uniform_blocks(&self) -> Ref<'_, [ActiveUniformBlockInfo]> {
Ref::map(self.active_uniform_blocks.borrow(), |blocks| &**blocks)
}
/// glValidateProgram
pub(crate) fn validate(&self) -> WebGLResult<()> {
if self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::ValidateProgram(self.id));
Ok(())
}
/// glAttachShader
pub(crate) fn attach_shader(&self, shader: &WebGLShader) -> WebGLResult<()> {
if self.is_deleted() || shader.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
let shader_slot = match shader.gl_type() {
constants::FRAGMENT_SHADER => &self.fragment_shader,
constants::VERTEX_SHADER => &self.vertex_shader,
_ => {
error!("detachShader: Unexpected shader type");
return Err(WebGLError::InvalidValue);
},
};
if shader_slot.get().is_some() {
return Err(WebGLError::InvalidOperation);
}
shader_slot.set(Some(shader));
shader.increment_attached_counter();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::AttachShader(self.id, shader.id()));
Ok(())
}
/// glDetachShader
pub(crate) fn detach_shader(&self, shader: &WebGLShader) -> WebGLResult<()> {
if self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
let shader_slot = match shader.gl_type() {
constants::FRAGMENT_SHADER => &self.fragment_shader,
constants::VERTEX_SHADER => &self.vertex_shader,
_ => return Err(WebGLError::InvalidValue),
};
match shader_slot.get() {
Some(ref attached_shader) if attached_shader.id() != shader.id() => {
return Err(WebGLError::InvalidOperation);
},
None => return Err(WebGLError::InvalidOperation),
_ => {},
}
shader_slot.set(None);
shader.decrement_attached_counter();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::DetachShader(self.id, shader.id()));
Ok(())
}
/// glBindAttribLocation
pub(crate) fn bind_attrib_location(&self, index: u32, name: DOMString) -> WebGLResult<()> {
if self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if !validate_glsl_name(&name)? {
return Ok(());
}
if name.starts_with_str("gl_") {
return Err(WebGLError::InvalidOperation);
}
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::BindAttribLocation(
self.id,
index,
name.into(),
));
Ok(())
}
pub(crate) fn get_active_uniform(
&self,
index: u32,
can_gc: CanGc,
) -> WebGLResult<DomRoot<WebGLActiveInfo>> {
if self.is_deleted() {
return Err(WebGLError::InvalidValue);
}
let uniforms = self.active_uniforms.borrow();
let data = uniforms
.get(index as usize)
.ok_or(WebGLError::InvalidValue)?;
Ok(WebGLActiveInfo::new(
self.global().as_window(),
data.size.unwrap_or(1),
data.type_,
data.name().into(),
can_gc,
))
}
/// glGetActiveAttrib
pub(crate) fn get_active_attrib(
&self,
index: u32,
can_gc: CanGc,
) -> WebGLResult<DomRoot<WebGLActiveInfo>> {
if self.is_deleted() {
return Err(WebGLError::InvalidValue);
}
let attribs = self.active_attribs.borrow();
let data = attribs
.get(index as usize)
.ok_or(WebGLError::InvalidValue)?;
Ok(WebGLActiveInfo::new(
self.global().as_window(),
data.size,
data.type_,
data.name.clone().into(),
can_gc,
))
}
/// glGetAttribLocation
pub(crate) fn get_attrib_location(&self, name: DOMString) -> WebGLResult<i32> {
if !self.is_linked() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if !validate_glsl_name(&name)? {
return Ok(-1);
}
if name.starts_with_str("gl_") {
return Ok(-1);
}
let location = self
.active_attribs
.borrow()
.iter()
.find(|attrib| *attrib.name == name)
.and_then(|attrib| attrib.location.map(|l| l as i32))
.unwrap_or(-1);
Ok(location)
}
/// glGetFragDataLocation
pub(crate) fn get_frag_data_location(&self, name: DOMString) -> WebGLResult<i32> {
if !self.is_linked() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if !validate_glsl_name(&name)? {
return Ok(-1);
}
if name.starts_with_str("gl_") {
return Ok(-1);
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::GetFragDataLocation(
self.id,
name.into(),
sender,
));
Ok(receiver.recv().unwrap())
}
/// glGetUniformLocation
pub(crate) fn get_uniform_location(
&self,
name: DOMString,
can_gc: CanGc,
) -> WebGLResult<Option<DomRoot<WebGLUniformLocation>>> {
if !self.is_linked() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if !validate_glsl_name(&name)? {
return Ok(None);
}
if name.starts_with_str("gl_") {
return Ok(None);
}
let (size, type_) = {
let (base_name, array_index) = match parse_uniform_name(&name) {
Some((name, index)) if index.is_none_or(|i| i >= 0) => (name, index),
_ => return Ok(None),
};
let uniforms = self.active_uniforms.borrow();
match uniforms
.iter()
.find(|attrib| *attrib.base_name == base_name)
{
Some(uniform) if array_index.is_none() || array_index < uniform.size => (
uniform
.size
.map(|size| size - array_index.unwrap_or_default()),
uniform.type_,
),
_ => return Ok(None),
}
};
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::GetUniformLocation(
self.id,
name.into(),
sender,
));
let location = receiver.recv().unwrap();
let context_id = self.upcast::<WebGLObject>().context().context_id();
Ok(Some(WebGLUniformLocation::new(
self.global().as_window(),
location,
context_id,
self.id,
self.link_generation.get(),
size,
type_,
can_gc,
)))
}
pub(crate) fn get_uniform_block_index(&self, name: DOMString) -> WebGLResult<u32> {
if !self.link_called.get() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if !validate_glsl_name(&name)? {
return Ok(constants2::INVALID_INDEX);
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::GetUniformBlockIndex(
self.id,
name.into(),
sender,
));
Ok(receiver.recv().unwrap())
}
pub(crate) fn get_uniform_indices(&self, names: Vec<DOMString>) -> WebGLResult<Vec<u32>> {
if !self.link_called.get() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
let validation_errors = names.iter().map(validate_glsl_name).collect::<Vec<_>>();
let first_validation_error = validation_errors.iter().find(|result| result.is_err());
if let Some(error) = first_validation_error {
return Err(error.unwrap_err());
}
let names = names
.iter()
.map(|name| name.to_string())
.collect::<Vec<_>>();
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::GetUniformIndices(self.id, names, sender));
Ok(receiver.recv().unwrap())
}
pub(crate) fn get_active_uniforms(
&self,
indices: Vec<u32>,
pname: u32,
) -> WebGLResult<Vec<i32>> {
if !self.is_linked() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
match pname {
constants2::UNIFORM_TYPE |
constants2::UNIFORM_SIZE |
constants2::UNIFORM_BLOCK_INDEX |
constants2::UNIFORM_OFFSET |
constants2::UNIFORM_ARRAY_STRIDE |
constants2::UNIFORM_MATRIX_STRIDE |
constants2::UNIFORM_IS_ROW_MAJOR => {},
_ => return Err(WebGLError::InvalidEnum),
}
if indices.len() > self.active_uniforms.borrow().len() {
return Err(WebGLError::InvalidValue);
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::GetActiveUniforms(
self.id, indices, pname, sender,
));
Ok(receiver.recv().unwrap())
}
pub(crate) fn get_active_uniform_block_parameter(
&self,
block_index: u32,
pname: u32,
) -> WebGLResult<Vec<i32>> {
if !self.link_called.get() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if block_index as usize >= self.active_uniform_blocks.borrow().len() {
return Err(WebGLError::InvalidValue);
}
match pname {
constants2::UNIFORM_BLOCK_BINDING |
constants2::UNIFORM_BLOCK_DATA_SIZE |
constants2::UNIFORM_BLOCK_ACTIVE_UNIFORMS |
constants2::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES |
constants2::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER |
constants2::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => {},
_ => return Err(WebGLError::InvalidEnum),
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>().context().send_command(
WebGLCommand::GetActiveUniformBlockParameter(self.id, block_index, pname, sender),
);
Ok(receiver.recv().unwrap())
}
pub(crate) fn get_active_uniform_block_name(&self, block_index: u32) -> WebGLResult<String> {
if !self.link_called.get() || self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
if block_index as usize >= self.active_uniform_blocks.borrow().len() {
return Err(WebGLError::InvalidValue);
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>().context().send_command(
WebGLCommand::GetActiveUniformBlockName(self.id, block_index, sender),
);
Ok(receiver.recv().unwrap())
}
pub(crate) fn bind_uniform_block(
&self,
block_index: u32,
block_binding: u32,
) -> WebGLResult<()> {
if block_index as usize >= self.active_uniform_blocks.borrow().len() {
return Err(WebGLError::InvalidValue);
}
let mut active_uniforms = self.active_uniforms.borrow_mut();
if active_uniforms.len() > block_binding as usize {
active_uniforms[block_binding as usize].bind_index = Some(block_binding);
}
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::UniformBlockBinding(
self.id,
block_index,
block_binding,
));
Ok(())
}
/// glGetProgramInfoLog
pub(crate) fn get_info_log(&self) -> WebGLResult<String> {
if self.is_deleted() {
return Err(WebGLError::InvalidValue);
}
if self.link_called.get() {
let shaders_compiled = match (self.fragment_shader.get(), self.vertex_shader.get()) {
(Some(fs), Some(vs)) => fs.successfully_compiled() && vs.successfully_compiled(),
_ => false,
};
if !shaders_compiled {
return Ok("One or more shaders failed to compile".to_string());
}
}
let (sender, receiver) = webgl_channel().unwrap();
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::GetProgramInfoLog(self.id, sender));
Ok(receiver.recv().unwrap())
}
pub(crate) fn attached_shaders(&self) -> WebGLResult<Vec<DomRoot<WebGLShader>>> {
if self.marked_for_deletion.get() {
return Err(WebGLError::InvalidValue);
}
Ok(
match (self.vertex_shader.get(), self.fragment_shader.get()) {
(Some(vertex_shader), Some(fragment_shader)) => {
vec![vertex_shader, fragment_shader]
},
(Some(shader), None) | (None, Some(shader)) => vec![shader],
(None, None) => vec![],
},
)
}
pub(crate) fn link_generation(&self) -> u64 {
self.link_generation.get()
}
pub(crate) fn transform_feedback_varyings_length(&self) -> i32 {
self.transform_feedback_varyings_length.get()
}
pub(crate) fn transform_feedback_buffer_mode(&self) -> i32 {
self.transform_feedback_mode.get()
}
}
impl Drop for WebGLProgram {
fn drop(&mut self) {
self.in_use(false);
self.mark_for_deletion(Operation::Fallible);
}
}
fn validate_glsl_name(name: &DOMString) -> WebGLResult<bool> {
if name.is_empty() {
return Ok(false);
}
if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN {
return Err(WebGLError::InvalidValue);
}
for c in name.str().chars() {
validate_glsl_char(c)?;
}
if name.starts_with_str("webgl_") || name.starts_with_str("_webgl_") {
return Err(WebGLError::InvalidOperation);
}
Ok(true)
}
fn validate_glsl_char(c: char) -> WebGLResult<()> {
match c {
'a'..='z' |
'A'..='Z' |
'0'..='9' |
' ' |
'\t' |
'\u{11}' |
'\u{12}' |
'\r' |
'\n' |
'_' |
'.' |
'+' |
'-' |
'/' |
'*' |
'%' |
'<' |
'>' |
'[' |
']' |
'(' |
')' |
'{' |
'}' |
'^' |
'|' |
'&' |
'~' |
'=' |
'!' |
':' |
';' |
',' |
'?' => Ok(()),
_ => Err(WebGLError::InvalidValue),
}
}
fn parse_uniform_name(name: &DOMString) -> Option<(String, Option<i32>)> {
let name = name.str();
if !name.ends_with(']') {
return Some((String::from(name), None));
}
let bracket_pos = name[..name.len() - 1].rfind('[')?;
let index = name[(bracket_pos + 1)..(name.len() - 1)]
.parse::<i32>()
.ok()?;
Some((String::from(&name[..bracket_pos]), Some(index)))
}
pub(crate) const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256;