diff options
Diffstat (limited to 'components/script/dom/webgltexture.rs')
-rw-r--r-- | components/script/dom/webgltexture.rs | 494 |
1 files changed, 332 insertions, 162 deletions
diff --git a/components/script/dom/webgltexture.rs b/components/script/dom/webgltexture.rs index d88f955a862..7456f772dec 100644 --- a/components/script/dom/webgltexture.rs +++ b/components/script/dom/webgltexture.rs @@ -1,27 +1,43 @@ /* 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 http://mozilla.org/MPL/2.0/. */ + * 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 canvas_traits::CanvasMsg; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; -use dom::bindings::codegen::Bindings::WebGLTextureBinding; -use dom::bindings::js::Root; -use dom::bindings::reflector::reflect_dom_object; -use dom::webgl_validations::types::{TexImageTarget, TexFormat, TexDataType}; -use dom::webglobject::WebGLObject; -use dom::window::Window; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants; +use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::Dom; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::webgl_validations::types::TexImageTarget; +use crate::dom::webglframebuffer::WebGLFramebuffer; +use crate::dom::webglobject::WebGLObject; +use crate::dom::webglrenderingcontext::{Operation, WebGLRenderingContext}; +use crate::dom::xrsession::XRSession; +use canvas_traits::webgl::{ + webgl_channel, TexDataType, TexFormat, TexParameter, TexParameterBool, TexParameterInt, + WebGLResult, WebGLTextureId, +}; +use canvas_traits::webgl::{DOMToTextureCommand, WebGLCommand, WebGLError}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; use std::cell::Cell; use std::cmp; -use webrender_traits; -use webrender_traits::{WebGLCommand, WebGLError, WebGLResult, WebGLTextureId}; pub enum TexParameterValue { Float(f32), Int(i32), + Bool(bool), +} + +// Textures generated for WebXR are owned by the WebXR device, not by the WebGL thread +// so the GL texture should not be deleted when the texture is garbage collected. +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +enum WebGLTextureOwner { + WebGL, + WebXR(Dom<XRSession>), } const MAX_LEVEL_COUNT: usize = 31; @@ -36,52 +52,77 @@ pub struct WebGLTexture { /// The target to which this texture was bound the first time target: Cell<Option<u32>>, is_deleted: Cell<bool>, + owner: WebGLTextureOwner, /// Stores information about mipmap levels and cubemap faces. - #[ignore_heap_size_of = "Arrays are cumbersome"] - image_info_array: DOMRefCell<[ImageInfo; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>, + #[ignore_malloc_size_of = "Arrays are cumbersome"] + image_info_array: DomRefCell<[Option<ImageInfo>; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>, /// Face count can only be 1 or 6 face_count: Cell<u8>, base_mipmap_level: u32, - #[ignore_heap_size_of = "Defined in ipc-channel"] - renderer: IpcSender<CanvasMsg>, + // Store information for min and mag filters + min_filter: Cell<u32>, + mag_filter: Cell<u32>, + /// True if this texture is used for the DOMToTexture feature. + attached_to_dom: Cell<bool>, + /// Framebuffer that this texture is attached to. + attached_framebuffer: MutNullableDom<WebGLFramebuffer>, + /// Number of immutable levels. + immutable_levels: Cell<Option<u32>>, } impl WebGLTexture { - fn new_inherited(renderer: IpcSender<CanvasMsg>, - id: WebGLTextureId) - -> WebGLTexture { - WebGLTexture { - webgl_object: WebGLObject::new_inherited(), + fn new_inherited( + context: &WebGLRenderingContext, + id: WebGLTextureId, + owner: Option<&XRSession>, + ) -> Self { + Self { + webgl_object: WebGLObject::new_inherited(context), id: id, target: Cell::new(None), is_deleted: Cell::new(false), + owner: owner + .map(|session| WebGLTextureOwner::WebXR(Dom::from_ref(session))) + .unwrap_or(WebGLTextureOwner::WebGL), + immutable_levels: Cell::new(None), face_count: Cell::new(0), base_mipmap_level: 0, - image_info_array: DOMRefCell::new([ImageInfo::new(); MAX_LEVEL_COUNT * MAX_FACE_COUNT]), - renderer: renderer, + min_filter: Cell::new(constants::NEAREST_MIPMAP_LINEAR), + mag_filter: Cell::new(constants::LINEAR), + image_info_array: DomRefCell::new([None; MAX_LEVEL_COUNT * MAX_FACE_COUNT]), + attached_to_dom: Cell::new(false), + attached_framebuffer: Default::default(), } } - pub fn maybe_new(window: &Window, renderer: IpcSender<CanvasMsg>) - -> Option<Root<WebGLTexture>> { - let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap(); - renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateTexture(sender))).unwrap(); + pub fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> { + let (sender, receiver) = webgl_channel().unwrap(); + context.send_command(WebGLCommand::CreateTexture(sender)); + receiver + .recv() + .unwrap() + .map(|id| WebGLTexture::new(context, id)) + } - let result = receiver.recv().unwrap(); - result.map(|texture_id| WebGLTexture::new(window, renderer, texture_id)) + pub fn new(context: &WebGLRenderingContext, id: WebGLTextureId) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLTexture::new_inherited(context, id, None)), + &*context.global(), + ) } - pub fn new(window: &Window, - renderer: IpcSender<CanvasMsg>, - id: WebGLTextureId) - -> Root<WebGLTexture> { - reflect_dom_object(box WebGLTexture::new_inherited(renderer, id), - window, - WebGLTextureBinding::Wrap) + pub fn new_webxr( + context: &WebGLRenderingContext, + id: WebGLTextureId, + session: &XRSession, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(WebGLTexture::new_inherited(context, id, Some(session))), + &*context.global(), + ) } } - impl WebGLTexture { pub fn id(&self) -> WebGLTextureId { self.id @@ -89,7 +130,7 @@ impl WebGLTexture { // NB: Only valid texture targets come here pub fn bind(&self, target: u32) -> WebGLResult<()> { - if self.is_deleted.get() { + if self.is_invalid() { return Err(WebGLError::InvalidOperation); } @@ -102,37 +143,44 @@ impl WebGLTexture { let face_count = match target { constants::TEXTURE_2D => 1, constants::TEXTURE_CUBE_MAP => 6, - _ => return Err(WebGLError::InvalidOperation) + _ => return Err(WebGLError::InvalidEnum), }; self.face_count.set(face_count); self.target.set(Some(target)); } - let msg = CanvasMsg::WebGL(WebGLCommand::BindTexture(target, Some(self.id))); - self.renderer.send(msg).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::BindTexture(target, Some(self.id))); Ok(()) } - pub fn initialize(&self, - target: TexImageTarget, - width: u32, - height: u32, - depth: u32, - internal_format: TexFormat, - level: u32, - data_type: Option<TexDataType>) -> WebGLResult<()> { + pub fn initialize( + &self, + target: TexImageTarget, + width: u32, + height: u32, + depth: u32, + internal_format: TexFormat, + level: u32, + data_type: Option<TexDataType>, + ) -> WebGLResult<()> { let image_info = ImageInfo { width: width, height: height, depth: depth, - internal_format: Some(internal_format), - is_initialized: true, + internal_format: internal_format, data_type: data_type, }; let face_index = self.face_index_for_target(&target); self.set_image_infos_at_level_and_face(level, face_index, image_info); + + if let Some(fb) = self.attached_framebuffer.get() { + fb.update_status(); + } + Ok(()) } @@ -142,13 +190,10 @@ impl WebGLTexture { None => { error!("Cannot generate mipmap on texture that has no target!"); return Err(WebGLError::InvalidOperation); - } + }, }; - let base_image_info = self.base_image_info().unwrap(); - if !base_image_info.is_initialized() { - return Err(WebGLError::InvalidOperation); - } + let base_image_info = self.base_image_info().ok_or(WebGLError::InvalidOperation)?; let is_cubic = target == constants::TEXTURE_CUBE_MAP; if is_cubic && !self.is_cube_complete() { @@ -163,7 +208,9 @@ impl WebGLTexture { return Err(WebGLError::InvalidOperation); } - self.renderer.send(CanvasMsg::WebGL(WebGLCommand::GenerateMipmap(target))).unwrap(); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::GenerateMipmap(target)); if self.base_mipmap_level + base_image_info.get_max_mimap_levels() == 0 { return Err(WebGLError::InvalidOperation); @@ -173,89 +220,157 @@ impl WebGLTexture { self.populate_mip_chain(self.base_mipmap_level, last_level) } - pub fn delete(&self) { + pub fn delete(&self, operation_fallibility: Operation) { if !self.is_deleted.get() { self.is_deleted.set(true); - let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteTexture(self.id))); + let context = self.upcast::<WebGLObject>().context(); + // Notify WR to release the frame output when using DOMToTexture feature + if self.attached_to_dom.get() { + let _ = context + .webgl_sender() + .send_dom_to_texture(DOMToTextureCommand::Detach(self.id)); + } + + /* + If a texture object is deleted while its image is attached to one or more attachment + points in a currently bound framebuffer, then it is as if FramebufferTexture had been + called, with a texture of zero, for each attachment point to which this im-age was + attached in that framebuffer. In other words, this texture image is firstdetached from + all attachment points in a currently bound framebuffer. + - GLES 3.0, 4.4.2.3, "Attaching Texture Images to a Framebuffer" + */ + if let Some(fb) = context.get_draw_framebuffer_slot().get() { + let _ = fb.detach_texture(self); + } + if let Some(fb) = context.get_read_framebuffer_slot().get() { + let _ = fb.detach_texture(self); + } + + // We don't delete textures owned by WebXR + if let WebGLTextureOwner::WebXR(_) = self.owner { + return; + } + + let cmd = WebGLCommand::DeleteTexture(self.id); + match operation_fallibility { + Operation::Fallible => context.send_command_ignored(cmd), + Operation::Infallible => context.send_command(cmd), + } } } - pub fn is_deleted(&self) -> bool { + pub fn is_invalid(&self) -> bool { + // https://immersive-web.github.io/layers/#xrwebglsubimagetype + if let WebGLTextureOwner::WebXR(ref session) = self.owner { + if session.is_outside_raf() { + return true; + } + } self.is_deleted.get() } + pub fn is_immutable(&self) -> bool { + self.immutable_levels.get().is_some() + } + pub fn target(&self) -> Option<u32> { self.target.get() } + pub fn maybe_get_tex_parameter(&self, param: TexParameter) -> Option<TexParameterValue> { + match param { + TexParameter::Int(TexParameterInt::TextureImmutableLevels) => Some( + TexParameterValue::Int(self.immutable_levels.get().unwrap_or(0) as i32), + ), + TexParameter::Bool(TexParameterBool::TextureImmutableFormat) => { + Some(TexParameterValue::Bool(self.is_immutable())) + }, + _ => None, + } + } + /// We have to follow the conversion rules for GLES 2.0. See: /// https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html /// - pub fn tex_parameter(&self, - target: u32, - name: u32, - value: TexParameterValue) -> WebGLResult<()> { - let (int_value, _float_value) = match value { + pub fn tex_parameter(&self, param: u32, value: TexParameterValue) -> WebGLResult<()> { + let target = self.target().unwrap(); + + let (int_value, float_value) = match value { TexParameterValue::Int(int_value) => (int_value, int_value as f32), TexParameterValue::Float(float_value) => (float_value as i32, float_value), + TexParameterValue::Bool(_) => unreachable!("no settable tex params should be booleans"), }; - match name { - constants::TEXTURE_MIN_FILTER => { - match int_value as u32 { - constants::NEAREST | - constants::LINEAR | - constants::NEAREST_MIPMAP_NEAREST | - constants::LINEAR_MIPMAP_NEAREST | - constants::NEAREST_MIPMAP_LINEAR | - constants::LINEAR_MIPMAP_LINEAR => { - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::TexParameteri(target, name, int_value))) - .unwrap(); - Ok(()) - }, - - _ => Err(WebGLError::InvalidEnum), - } + let update_filter = |filter: &Cell<u32>| { + if filter.get() == int_value as u32 { + return Ok(()); + } + filter.set(int_value as u32); + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::TexParameteri(target, param, int_value)); + Ok(()) + }; + match param { + constants::TEXTURE_MIN_FILTER => match int_value as u32 { + constants::NEAREST | + constants::LINEAR | + constants::NEAREST_MIPMAP_NEAREST | + constants::LINEAR_MIPMAP_NEAREST | + constants::NEAREST_MIPMAP_LINEAR | + constants::LINEAR_MIPMAP_LINEAR => update_filter(&self.min_filter), + _ => Err(WebGLError::InvalidEnum), }, - constants::TEXTURE_MAG_FILTER => { - match int_value as u32 { - constants::NEAREST | - constants::LINEAR => { - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::TexParameteri(target, name, int_value))) - .unwrap(); - Ok(()) - }, - - _ => Err(WebGLError::InvalidEnum), - } + constants::TEXTURE_MAG_FILTER => match int_value as u32 { + constants::NEAREST | constants::LINEAR => update_filter(&self.mag_filter), + _ => return Err(WebGLError::InvalidEnum), + }, + constants::TEXTURE_WRAP_S | constants::TEXTURE_WRAP_T => match int_value as u32 { + constants::CLAMP_TO_EDGE | constants::MIRRORED_REPEAT | constants::REPEAT => { + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::TexParameteri(target, param, int_value)); + Ok(()) + }, + _ => Err(WebGLError::InvalidEnum), }, - constants::TEXTURE_WRAP_S | - constants::TEXTURE_WRAP_T => { - match int_value as u32 { - constants::CLAMP_TO_EDGE | - constants::MIRRORED_REPEAT | - constants::REPEAT => { - self.renderer - .send(CanvasMsg::WebGL(WebGLCommand::TexParameteri(target, name, int_value))) - .unwrap(); - Ok(()) - }, - - _ => Err(WebGLError::InvalidEnum), + EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT => { + // NaN is not less than 1., what a time to be alive. + if !(float_value >= 1.) { + return Err(WebGLError::InvalidValue); } + self.upcast::<WebGLObject>() + .context() + .send_command(WebGLCommand::TexParameterf(target, param, float_value)); + Ok(()) }, - _ => Err(WebGLError::InvalidEnum), } } + pub fn min_filter(&self) -> u32 { + self.min_filter.get() + } + + pub fn mag_filter(&self) -> u32 { + self.mag_filter.get() + } + + pub fn is_using_linear_filtering(&self) -> bool { + let filters = [self.min_filter.get(), self.mag_filter.get()]; + filters.iter().any(|filter| match *filter { + constants::LINEAR | + constants::NEAREST_MIPMAP_LINEAR | + constants::LINEAR_MIPMAP_NEAREST | + constants::LINEAR_MIPMAP_LINEAR => true, + _ => false, + }) + } + pub fn populate_mip_chain(&self, first_level: u32, last_level: u32) -> WebGLResult<()> { - let base_image_info = self.image_info_at_face(0, first_level); - if !base_image_info.is_initialized() { - return Err(WebGLError::InvalidOperation); - } + let base_image_info = self + .image_info_at_face(0, first_level) + .ok_or(WebGLError::InvalidOperation)?; let mut ref_width = base_image_info.width; let mut ref_height = base_image_info.height; @@ -277,7 +392,6 @@ impl WebGLTexture { height: ref_height, depth: 0, internal_format: base_image_info.internal_format, - is_initialized: base_image_info.is_initialized(), data_type: base_image_info.data_type, }; @@ -287,26 +401,27 @@ impl WebGLTexture { } fn is_cube_complete(&self) -> bool { - debug_assert!(self.face_count.get() == 6); + debug_assert_eq!(self.face_count.get(), 6); - let image_info = self.base_image_info().unwrap(); - if !image_info.is_defined() { - return false; - } + let image_info = match self.base_image_info() { + Some(info) => info, + None => return false, + }; let ref_width = image_info.width; let ref_format = image_info.internal_format; for face in 0..self.face_count.get() { - let current_image_info = self.image_info_at_face(face, self.base_mipmap_level); - if !current_image_info.is_defined() { - return false; - } + let current_image_info = match self.image_info_at_face(face, self.base_mipmap_level) { + Some(info) => info, + None => return false, + }; // Compares height with width to enforce square dimensions if current_image_info.internal_format != ref_format || - current_image_info.width != ref_width || - current_image_info.height != ref_width { + current_image_info.width != ref_width || + current_image_info.height != ref_width + { return false; } } @@ -314,27 +429,24 @@ impl WebGLTexture { true } - fn face_index_for_target(&self, - target: &TexImageTarget) -> u8 { + fn face_index_for_target(&self, target: &TexImageTarget) -> u8 { match *target { - TexImageTarget::Texture2D => 0, TexImageTarget::CubeMapPositiveX => 0, TexImageTarget::CubeMapNegativeX => 1, TexImageTarget::CubeMapPositiveY => 2, TexImageTarget::CubeMapNegativeY => 3, TexImageTarget::CubeMapPositiveZ => 4, TexImageTarget::CubeMapNegativeZ => 5, + _ => 0, } } - pub fn image_info_for_target(&self, - target: &TexImageTarget, - level: u32) -> ImageInfo { + pub fn image_info_for_target(&self, target: &TexImageTarget, level: u32) -> Option<ImageInfo> { let face_index = self.face_index_for_target(&target); self.image_info_at_face(face_index, level) } - pub fn image_info_at_face(&self, face: u8, level: u32) -> ImageInfo { + pub fn image_info_at_face(&self, face: u8, level: u32) -> Option<ImageInfo> { let pos = (level * self.face_count.get() as u32) + face as u32; self.image_info_array.borrow()[pos as usize] } @@ -348,44 +460,96 @@ impl WebGLTexture { fn set_image_infos_at_level_and_face(&self, level: u32, face: u8, image_info: ImageInfo) { debug_assert!(face < self.face_count.get()); let pos = (level * self.face_count.get() as u32) + face as u32; - self.image_info_array.borrow_mut()[pos as usize] = image_info; + self.image_info_array.borrow_mut()[pos as usize] = Some(image_info); } fn base_image_info(&self) -> Option<ImageInfo> { assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT); - Some(self.image_info_at_face(0, self.base_mipmap_level)) + self.image_info_at_face(0, self.base_mipmap_level) + } + + pub fn set_attached_to_dom(&self) { + self.attached_to_dom.set(true); + } + + pub fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) { + self.attached_framebuffer.set(Some(fb)); + } + + pub fn detach_from_framebuffer(&self) { + self.attached_framebuffer.set(None); + } + + pub fn storage( + &self, + target: TexImageTarget, + levels: u32, + internal_format: TexFormat, + width: u32, + height: u32, + depth: u32, + ) -> WebGLResult<()> { + // Handled by the caller + assert!(!self.is_immutable()); + assert!(self.target().is_some()); + + let target_id = target.as_gl_constant(); + let command = match target { + TexImageTarget::Texture2D | TexImageTarget::CubeMap => { + WebGLCommand::TexStorage2D(target_id, levels, internal_format, width, height) + }, + TexImageTarget::Texture3D | TexImageTarget::Texture2DArray => { + WebGLCommand::TexStorage3D(target_id, levels, internal_format, width, height, depth) + }, + _ => unreachable!(), // handled by the caller + }; + self.upcast::<WebGLObject>().context().send_command(command); + + let mut width = width; + let mut height = height; + let mut depth = depth; + for level in 0..levels { + let image_info = ImageInfo { + width, + height, + depth, + internal_format, + data_type: None, + }; + self.set_image_infos_at_level(level, image_info); + + width = cmp::max(1, width / 2); + height = cmp::max(1, height / 2); + depth = cmp::max(1, depth / 2); + } + + self.immutable_levels.set(Some(levels)); + + if let Some(fb) = self.attached_framebuffer.get() { + fb.update_status(); + } + + Ok(()) } } impl Drop for WebGLTexture { fn drop(&mut self) { - self.delete(); + self.delete(Operation::Fallible); } } -#[derive(Clone, Copy, PartialEq, Debug, JSTraceable, HeapSizeOf)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub struct ImageInfo { width: u32, height: u32, depth: u32, - internal_format: Option<TexFormat>, - is_initialized: bool, + internal_format: TexFormat, data_type: Option<TexDataType>, } impl ImageInfo { - fn new() -> ImageInfo { - ImageInfo { - width: 0, - height: 0, - depth: 0, - internal_format: None, - is_initialized: false, - data_type: None, - } - } - pub fn width(&self) -> u32 { self.width } @@ -394,7 +558,7 @@ impl ImageInfo { self.height } - pub fn internal_format(&self) -> Option<TexFormat> { + pub fn internal_format(&self) -> TexFormat { self.internal_format } @@ -404,16 +568,8 @@ impl ImageInfo { fn is_power_of_two(&self) -> bool { self.width.is_power_of_two() && - self.height.is_power_of_two() && - self.depth.is_power_of_two() - } - - fn is_initialized(&self) -> bool { - self.is_initialized - } - - fn is_defined(&self) -> bool { - self.internal_format.is_some() + self.height.is_power_of_two() && + self.depth.is_power_of_two() } fn get_max_mimap_levels(&self) -> u32 { @@ -426,7 +582,21 @@ impl ImageInfo { } fn is_compressed_format(&self) -> bool { - // TODO: Once Servo supports compressed formats, check for them here - false + self.internal_format.is_compressed() } } + +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] +pub enum TexCompressionValidation { + None, + S3TC, +} + +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)] +pub struct TexCompression { + pub format: TexFormat, + pub bytes_per_block: u8, + pub block_width: u8, + pub block_height: u8, + pub validation: TexCompressionValidation, +} |