mirror of
https://github.com/servo/servo
synced 2026-05-09 00:22:16 +02:00
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>
867 lines
28 KiB
Rust
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;
|