/* 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/. */ // https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl use canvas_traits::webgl::{webgl_channel, WebGLCommand, WebGLError, WebGLMsgSender, WebGLResult, WebGLTextureId}; use canvas_traits::webgl::DOMToTextureCommand; use dom::bindings::cell::DomRefCell; use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants; use dom::bindings::codegen::Bindings::WebGLTextureBinding; use dom::bindings::reflector::reflect_dom_object; use dom::bindings::root::DomRoot; use dom::webgl_validations::types::{TexImageTarget, TexFormat, TexDataType}; use dom::webglobject::WebGLObject; use dom::window::Window; use dom_struct::dom_struct; use std::cell::Cell; use std::cmp; pub enum TexParameterValue { Float(f32), Int(i32), } const MAX_LEVEL_COUNT: usize = 31; const MAX_FACE_COUNT: usize = 6; jsmanaged_array!(MAX_LEVEL_COUNT * MAX_FACE_COUNT); #[dom_struct] pub struct WebGLTexture { webgl_object: WebGLObject, id: WebGLTextureId, /// The target to which this texture was bound the first time target: Cell>, is_deleted: Cell, /// Stores information about mipmap levels and cubemap faces. #[ignore_malloc_size_of = "Arrays are cumbersome"] image_info_array: DomRefCell<[ImageInfo; MAX_LEVEL_COUNT * MAX_FACE_COUNT]>, /// Face count can only be 1 or 6 face_count: Cell, base_mipmap_level: u32, // Store information for min and mag filters min_filter: Cell>, mag_filter: Cell>, #[ignore_malloc_size_of = "Defined in ipc-channel"] renderer: WebGLMsgSender, /// True if this texture is used for the DOMToTexture feature. attached_to_dom: Cell, } impl WebGLTexture { fn new_inherited(renderer: WebGLMsgSender, id: WebGLTextureId) -> WebGLTexture { WebGLTexture { webgl_object: WebGLObject::new_inherited(), id: id, target: Cell::new(None), is_deleted: Cell::new(false), face_count: Cell::new(0), base_mipmap_level: 0, min_filter: Cell::new(None), mag_filter: Cell::new(None), image_info_array: DomRefCell::new([ImageInfo::new(); MAX_LEVEL_COUNT * MAX_FACE_COUNT]), renderer: renderer, attached_to_dom: Cell::new(false), } } pub fn maybe_new(window: &Window, renderer: WebGLMsgSender) -> Option> { let (sender, receiver) = webgl_channel().unwrap(); renderer.send(WebGLCommand::CreateTexture(sender)).unwrap(); let result = receiver.recv().unwrap(); result.map(|texture_id| WebGLTexture::new(window, renderer, texture_id)) } pub fn new(window: &Window, renderer: WebGLMsgSender, id: WebGLTextureId) -> DomRoot { reflect_dom_object(Box::new(WebGLTexture::new_inherited(renderer, id)), window, WebGLTextureBinding::Wrap) } } impl WebGLTexture { pub fn id(&self) -> WebGLTextureId { self.id } // NB: Only valid texture targets come here pub fn bind(&self, target: u32) -> WebGLResult<()> { if self.is_deleted.get() { return Err(WebGLError::InvalidOperation); } if let Some(previous_target) = self.target.get() { if target != previous_target { return Err(WebGLError::InvalidOperation); } } else { // This is the first time binding let face_count = match target { constants::TEXTURE_2D => 1, constants::TEXTURE_CUBE_MAP => 6, _ => return Err(WebGLError::InvalidOperation) }; self.face_count.set(face_count); self.target.set(Some(target)); } let msg = WebGLCommand::BindTexture(target, Some(self.id)); self.renderer.send(msg).unwrap(); Ok(()) } pub fn initialize(&self, target: TexImageTarget, width: u32, height: u32, depth: u32, internal_format: TexFormat, level: u32, data_type: Option) -> WebGLResult<()> { let image_info = ImageInfo { width: width, height: height, depth: depth, internal_format: Some(internal_format), is_initialized: true, 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); Ok(()) } pub fn generate_mipmap(&self) -> WebGLResult<()> { let target = match self.target.get() { Some(target) => target, 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 is_cubic = target == constants::TEXTURE_CUBE_MAP; if is_cubic && !self.is_cube_complete() { return Err(WebGLError::InvalidOperation); } if !base_image_info.is_power_of_two() { return Err(WebGLError::InvalidOperation); } if base_image_info.is_compressed_format() { return Err(WebGLError::InvalidOperation); } self.renderer.send(WebGLCommand::GenerateMipmap(target)).unwrap(); if self.base_mipmap_level + base_image_info.get_max_mimap_levels() == 0 { return Err(WebGLError::InvalidOperation); } let last_level = self.base_mipmap_level + base_image_info.get_max_mimap_levels() - 1; self.populate_mip_chain(self.base_mipmap_level, last_level) } pub fn delete(&self) { if !self.is_deleted.get() { self.is_deleted.set(true); // Notify WR to release the frame output when using DOMToTexture feature if self.attached_to_dom.get() { let _ = self.renderer.send_dom_to_texture(DOMToTextureCommand::Detach(self.id)); } let _ = self.renderer.send(WebGLCommand::DeleteTexture(self.id)); } } pub fn is_deleted(&self) -> bool { self.is_deleted.get() } pub fn target(&self) -> Option { self.target.get() } /// 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 { TexParameterValue::Int(int_value) => (int_value, int_value as f32), TexParameterValue::Float(float_value) => (float_value as i32, float_value), }; 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.min_filter.set(Some(int_value as u32)); self.renderer .send(WebGLCommand::TexParameteri(target, name, int_value)) .unwrap(); Ok(()) }, _ => Err(WebGLError::InvalidEnum), } }, constants::TEXTURE_MAG_FILTER => { match int_value as u32 { constants::NEAREST | constants::LINEAR => { self.mag_filter.set(Some(int_value as u32)); self.renderer .send(WebGLCommand::TexParameteri(target, name, int_value)) .unwrap(); 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(WebGLCommand::TexParameteri(target, name, int_value)) .unwrap(); Ok(()) }, _ => Err(WebGLError::InvalidEnum), } }, _ => Err(WebGLError::InvalidEnum), } } pub fn is_using_linear_filtering(&self) -> bool { let filters = [self.min_filter.get(), self.mag_filter.get()]; filters.iter().any(|filter| { match *filter { Some(constants::LINEAR) | Some(constants::NEAREST_MIPMAP_LINEAR) | Some(constants::LINEAR_MIPMAP_NEAREST) | Some(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 mut ref_width = base_image_info.width; let mut ref_height = base_image_info.height; if ref_width == 0 || ref_height == 0 { return Err(WebGLError::InvalidOperation); } for level in (first_level + 1)..last_level { if ref_width == 1 && ref_height == 1 { break; } ref_width = cmp::max(1, ref_width / 2); ref_height = cmp::max(1, ref_height / 2); let image_info = ImageInfo { width: ref_width, 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, }; self.set_image_infos_at_level(level, image_info); } Ok(()) } fn is_cube_complete(&self) -> bool { debug_assert!(self.face_count.get() == 6); let image_info = self.base_image_info().unwrap(); if !image_info.is_defined() { 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; } // 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 { return false; } } true } 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, } } pub fn image_info_for_target(&self, target: &TexImageTarget, level: u32) -> 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 { let pos = (level * self.face_count.get() as u32) + face as u32; self.image_info_array.borrow()[pos as usize] } fn set_image_infos_at_level(&self, level: u32, image_info: ImageInfo) { for face in 0..self.face_count.get() { self.set_image_infos_at_level_and_face(level, face, image_info); } } 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; } fn base_image_info(&self) -> Option { assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT); Some(self.image_info_at_face(0, self.base_mipmap_level)) } pub fn set_attached_to_dom(&self) { self.attached_to_dom.set(true); } } impl Drop for WebGLTexture { fn drop(&mut self) { self.delete(); } } #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub struct ImageInfo { width: u32, height: u32, depth: u32, internal_format: Option, is_initialized: bool, data_type: Option, } 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 } pub fn height(&self) -> u32 { self.height } pub fn internal_format(&self) -> Option { self.internal_format } pub fn data_type(&self) -> Option { self.data_type } 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() } pub fn is_initialized(&self) -> bool { self.is_initialized } fn is_defined(&self) -> bool { self.internal_format.is_some() } fn get_max_mimap_levels(&self) -> u32 { let largest = cmp::max(cmp::max(self.width, self.height), self.depth); if largest == 0 { return 0; } // FloorLog2(largest) + 1 (largest as f64).log2() as u32 + 1 } fn is_compressed_format(&self) -> bool { // TODO: Once Servo supports compressed formats, check for them here false } }