Files
servo/components/script/dom/webgl/webglprogram.rs
Sam 0b94f0a7ce script: Report associated memory for webgl objects (#42570)
As started in https://github.com/servo/servo/issues/42168 let's report
memory pressure for webgl objects. CanvasContexts report memory twice:
once because of underlying texture object and once by themself, but
that's okay as we also need to account for swapchain textures.

Computing exact size would be to much work and code so we report rough
estimations.

Testing: None

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
2026-02-25 10:29:10 +00:00

867 lines
28 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, RefCell};
use std::collections::HashSet;
use canvas_traits::webgl::{
ActiveAttribInfo, ActiveUniformBlockInfo, ActiveUniformInfo, WebGLCommand, WebGLError,
WebGLProgramId, WebGLResult, webgl_channel,
};
use dom_struct::dom_struct;
use script_bindings::weakref::WeakRef;
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::dom::webglrenderingcontext::capture_webgl_backtrace;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
struct DroppableWebGLProgram {
#[no_trace]
id: WebGLProgramId,
context: WeakRef<WebGLRenderingContext>,
fragment_shader: Option<WeakRef<WebGLShader>>,
vertex_shader: Option<WeakRef<WebGLShader>>,
marked_for_deletion: bool,
is_in_use: bool,
}
impl DroppableWebGLProgram {
fn new(id: WebGLProgramId, context: &WebGLRenderingContext) -> Self {
Self {
id,
context: WeakRef::new(context),
fragment_shader: None,
vertex_shader: None,
marked_for_deletion: Default::default(),
is_in_use: Default::default(),
}
}
}
impl DroppableWebGLProgram {
fn attach_shader<'a>(&mut self, shader: &'a WebGLShader) -> WebGLResult<&'a WebGLShader> {
if self.is_deleted() || shader.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
let shader_slot = match shader.gl_type() {
constants::FRAGMENT_SHADER => &mut self.fragment_shader,
constants::VERTEX_SHADER => &mut self.vertex_shader,
_ => {
error!("detachShader: Unexpected shader type");
return Err(WebGLError::InvalidValue);
},
};
if shader_slot.is_some() {
return Err(WebGLError::InvalidOperation);
}
*shader_slot = Some(WeakRef::new(shader));
shader.increment_attached_counter();
self.send_command(WebGLCommand::AttachShader(self.id, shader.id()));
Ok(shader)
}
fn detach_shader<'a>(&mut self, shader: &'a WebGLShader) -> WebGLResult<&'a WebGLShader> {
if self.is_deleted() {
return Err(WebGLError::InvalidOperation);
}
let shader_slot = match shader.gl_type() {
constants::FRAGMENT_SHADER => &mut self.fragment_shader,
constants::VERTEX_SHADER => &mut self.vertex_shader,
_ => return Err(WebGLError::InvalidValue),
};
match shader_slot {
Some(attached_shader) => match attached_shader.root() {
Some(root) => {
if root.id() != shader.id() {
return Err(WebGLError::InvalidOperation);
}
},
None => return Err(WebGLError::InvalidOperation),
},
None => return Err(WebGLError::InvalidOperation),
}
*shader_slot = None;
shader.decrement_attached_counter();
self.send_command(WebGLCommand::DetachShader(self.id, shader.id()));
Ok(shader)
}
fn detach_shaders(&mut self) {
if let Some(ref mut shader) = self.fragment_shader {
if let Some(root) = shader.root() {
root.decrement_attached_counter();
self.send_command(WebGLCommand::DetachShader(self.id, root.id()));
}
self.fragment_shader = None;
}
if let Some(ref mut shader) = self.vertex_shader {
if let Some(root) = shader.root() {
root.decrement_attached_counter();
self.send_command(WebGLCommand::DetachShader(self.id, root.id()));
}
self.vertex_shader = None;
}
}
fn is_deleted(&self) -> bool {
self.marked_for_deletion && !self.is_in_use
}
fn send_command(&self, command: WebGLCommand) {
self.send_with_fallibility(command, Operation::Infallible);
}
fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
if let Some(root) = self.context.root() {
let result = root.sender().send(command, capture_webgl_backtrace());
if matches!(fallibility, Operation::Infallible) {
result.expect("Operation failed");
}
}
}
fn mark_for_deletion(&mut self, operation_fallibility: Operation) {
if self.marked_for_deletion {
return;
}
self.marked_for_deletion = true;
self.send_with_fallibility(WebGLCommand::DeleteProgram(self.id), operation_fallibility);
if self.is_deleted() {
self.detach_shaders();
}
}
fn in_use(&mut self, value: bool) {
if self.is_in_use == value {
return;
}
self.is_in_use = value;
if self.is_deleted() {
self.detach_shaders();
}
}
}
impl Drop for DroppableWebGLProgram {
fn drop(&mut self) {
self.in_use(false);
self.mark_for_deletion(Operation::Fallible);
}
}
#[dom_struct(associated_memory)]
pub(crate) struct WebGLProgram {
webgl_object: WebGLObject,
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>,
droppable: RefCell<DroppableWebGLProgram>,
}
impl WebGLProgram {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLProgramId) -> Self {
Self {
webgl_object: WebGLObject::new_inherited(context),
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(),
droppable: RefCell::new(DroppableWebGLProgram::new(id, context)),
}
}
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.droppable.borrow().id
}
/// glDeleteProgram
pub(crate) fn mark_for_deletion(&self, operation_fallibility: Operation) {
if self.is_marked_for_deletion() {
return;
}
self.set_marked_for_deletion(true);
self.upcast().send_with_fallibility(
WebGLCommand::DeleteProgram(self.id()),
operation_fallibility,
);
if self.is_deleted() {
self.detach_shaders();
}
}
pub(crate) fn in_use(&self, value: bool) {
if self.is_in_use() == value {
return;
}
self.set_is_in_use(value);
if self.is_deleted() {
self.detach_shaders();
}
}
fn detach_shaders(&self) {
assert!(self.is_deleted());
self.droppable.borrow_mut().detach_shaders();
if self.fragment_shader.get().is_some() {
self.fragment_shader.set(None);
}
if self.vertex_shader.get().is_some() {
self.vertex_shader.set(None);
}
}
pub(crate) fn is_in_use(&self) -> bool {
self.droppable.borrow().is_in_use
}
pub(crate) fn is_marked_for_deletion(&self) -> bool {
self.droppable.borrow().marked_for_deletion
}
pub(crate) fn is_deleted(&self) -> bool {
self.is_marked_for_deletion() && !self.is_in_use()
}
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()
.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()
.send_command(WebGLCommand::ValidateProgram(self.id()));
Ok(())
}
/// glAttachShader
pub(crate) fn attach_shader(&self, shader: &WebGLShader) -> WebGLResult<()> {
match self.droppable.borrow_mut().attach_shader(shader) {
Ok(shader) => {
let shader_slot = match shader.gl_type() {
constants::FRAGMENT_SHADER => &self.fragment_shader,
constants::VERTEX_SHADER => &self.vertex_shader,
_ => {
error!("attach_shader: Unexpected shader type");
return Err(WebGLError::InvalidValue);
},
};
shader_slot.set(Some(shader));
Ok(())
},
Err(e) => Err(e),
}
}
/// glDetachShader
pub(crate) fn detach_shader(&self, shader: &WebGLShader) -> WebGLResult<()> {
match self.droppable.borrow_mut().detach_shader(shader) {
Ok(shader) => {
let shader_slot = match shader.gl_type() {
constants::FRAGMENT_SHADER => &self.fragment_shader,
constants::VERTEX_SHADER => &self.vertex_shader,
_ => {
error!("detach_shader: Unexpected shader type");
return Err(WebGLError::InvalidValue);
},
};
shader_slot.set(None);
Ok(())
},
Err(e) => Err(e),
}
}
/// 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().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()
.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().send_command(WebGLCommand::GetUniformLocation(
self.id(),
name.into(),
sender,
));
let location = receiver.recv().unwrap();
let context_id = self.upcast().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()
.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()
.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().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()
.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()
.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()
.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()
.send_command(WebGLCommand::GetProgramInfoLog(self.id(), sender));
Ok(receiver.recv().unwrap())
}
pub(crate) fn attached_shaders(&self) -> WebGLResult<Vec<DomRoot<WebGLShader>>> {
if self.is_marked_for_deletion() {
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()
}
fn set_marked_for_deletion(&self, value: bool) {
self.droppable.borrow_mut().marked_for_deletion = value
}
fn set_is_in_use(&self, value: bool) {
self.droppable.borrow_mut().is_in_use = value
}
}
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;