servo/components/script/dom/webgltexture.rs
Simon Sapin aa15dc269f Remove use of unstable box syntax.
http://www.robohornet.org gives a score of 101.36 on master,
and 102.68 with this PR. The latter is slightly better,
but probably within noise level.
So it looks like this PR does not affect DOM performance.

This is expected since `Box::new` is defined as:

```rust
impl<T> Box<T> {
    #[inline(always)]
    pub fn new(x: T) -> Box<T> {
        box x
    }
}
```

With inlining, it should compile to the same as box syntax.
2017-10-16 17:16:20 +02:00

450 lines
14 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::webgl::{webgl_channel, WebGLCommand, WebGLError, WebGLMsgSender, WebGLResult, WebGLTextureId};
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<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: WebGLMsgSender,
}
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,
}
}
pub fn maybe_new(window: &Window, renderer: WebGLMsgSender)
-> Option<DomRoot<WebGLTexture>> {
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<WebGLTexture> {
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<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(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(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(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<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, Debug, HeapSizeOf, JSTraceable, PartialEq)]
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
}
}