forked from mirrors/gecko-dev
		
	 6e335dd92c
			
		
	
	
		6e335dd92c
		
	
	
	
	
		
			
			<!-- Please describe your changes on the following line: --> This PR provides the base for implementing WebGL extensions. It comes with the following ones already implemented and passing all related WebGL conformance tests: - OES_texture_float - OES_texture_float_linear - OES_texture_half_float - OES_texture_half_float_linear - OES_vertex_array_object I'll submit other extensions like compressed textures in a separate PR to ease the review process. I included the 5 extensions in this PR because it's easier to show/review how the WebGL extension base works. To pass all OES_texture_float_xxx tests I had to add some missing format conversions in WebGLRenderingContext.rs --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [x] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: b0976566fb9c79f7dc67b2ac808eb50ef4ad653f --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : e829ac1c4a43670d9931be4fafa4572d0c527210
		
			
				
	
	
		
			452 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			452 lines
		
	
	
	
		
			15 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 http://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 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),
 | |
| }
 | |
| 
 | |
| 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<Option<u32>>,
 | |
|     is_deleted: Cell<bool>,
 | |
|     /// 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]>,
 | |
|     /// Face count can only be 1 or 6
 | |
|     face_count: Cell<u8>,
 | |
|     base_mipmap_level: u32,
 | |
|     // Store information for min and mag filters
 | |
|     min_filter: Cell<Option<u32>>,
 | |
|     mag_filter: Cell<Option<u32>>,
 | |
|     #[ignore_heap_size_of = "Defined in ipc-channel"]
 | |
|     renderer: IpcSender<CanvasMsg>,
 | |
| }
 | |
| 
 | |
| impl WebGLTexture {
 | |
|     fn new_inherited(renderer: IpcSender<CanvasMsg>,
 | |
|                      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,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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();
 | |
| 
 | |
|         let result = receiver.recv().unwrap();
 | |
|         result.map(|texture_id| WebGLTexture::new(window, renderer, texture_id))
 | |
|     }
 | |
| 
 | |
|     pub fn new(window: &Window,
 | |
|                renderer: IpcSender<CanvasMsg>,
 | |
|                id: WebGLTextureId)
 | |
|                -> Root<WebGLTexture> {
 | |
|         reflect_dom_object(box 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 = CanvasMsg::WebGL(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<TexDataType>) -> 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(CanvasMsg::WebGL(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);
 | |
|             let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteTexture(self.id)));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn is_deleted(&self) -> bool {
 | |
|         self.is_deleted.get()
 | |
|     }
 | |
| 
 | |
|     pub fn target(&self) -> Option<u32> {
 | |
|         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(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 => {
 | |
|                         self.mag_filter.set(Some(int_value as u32));
 | |
|                         self.renderer
 | |
|                             .send(CanvasMsg::WebGL(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(CanvasMsg::WebGL(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<ImageInfo> {
 | |
|         assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT);
 | |
| 
 | |
|         Some(self.image_info_at_face(0, self.base_mipmap_level))
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Drop for WebGLTexture {
 | |
|     fn drop(&mut self) {
 | |
|         self.delete();
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Clone, Copy, PartialEq, Debug, JSTraceable, HeapSizeOf)]
 | |
| pub struct ImageInfo {
 | |
|     width: u32,
 | |
|     height: u32,
 | |
|     depth: u32,
 | |
|     internal_format: Option<TexFormat>,
 | |
|     is_initialized: bool,
 | |
|     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
 | |
|     }
 | |
| 
 | |
|     pub fn height(&self) -> u32 {
 | |
|         self.height
 | |
|     }
 | |
| 
 | |
|     pub fn internal_format(&self) -> Option<TexFormat> {
 | |
|         self.internal_format
 | |
|     }
 | |
| 
 | |
|     pub fn data_type(&self) -> Option<TexDataType> {
 | |
|         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
 | |
|     }
 | |
| }
 |