mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
476 lines
16 KiB
Rust
476 lines
16 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 crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants;
|
|
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
|
|
use crate::dom::bindings::codegen::Bindings::WebGLTextureBinding;
|
|
use crate::dom::bindings::inheritance::Castable;
|
|
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
|
use crate::dom::bindings::root::DomRoot;
|
|
use crate::dom::webgl_validations::types::TexImageTarget;
|
|
use crate::dom::webglobject::WebGLObject;
|
|
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
|
|
use canvas_traits::webgl::{webgl_channel, TexDataType, TexFormat, WebGLResult, WebGLTextureId};
|
|
use canvas_traits::webgl::{DOMToTextureCommand, WebGLCommand, WebGLError};
|
|
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_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<u8>,
|
|
base_mipmap_level: u32,
|
|
// 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>,
|
|
}
|
|
|
|
impl WebGLTexture {
|
|
fn new_inherited(context: &WebGLRenderingContext, id: WebGLTextureId) -> Self {
|
|
Self {
|
|
webgl_object: WebGLObject::new_inherited(context),
|
|
id: id,
|
|
target: Cell::new(None),
|
|
is_deleted: Cell::new(false),
|
|
face_count: Cell::new(0),
|
|
base_mipmap_level: 0,
|
|
min_filter: Cell::new(constants::NEAREST_MIPMAP_LINEAR),
|
|
mag_filter: Cell::new(constants::LINEAR),
|
|
image_info_array: DomRefCell::new([ImageInfo::new(); MAX_LEVEL_COUNT * MAX_FACE_COUNT]),
|
|
attached_to_dom: Cell::new(false),
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
pub fn new(context: &WebGLRenderingContext, id: WebGLTextureId) -> DomRoot<Self> {
|
|
reflect_dom_object(
|
|
Box::new(WebGLTexture::new_inherited(context, id)),
|
|
&*context.global(),
|
|
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::InvalidEnum),
|
|
};
|
|
self.face_count.set(face_count);
|
|
self.target.set(Some(target));
|
|
}
|
|
|
|
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<()> {
|
|
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();
|
|
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.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);
|
|
}
|
|
|
|
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 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 the currently
|
|
bound framebuffer, then it is as if FramebufferTexture2D had been called, with
|
|
a texture of 0, for each attachment point to which this image was attached
|
|
in the currently bound framebuffer.
|
|
- GLES 2.0, 4.4.3, "Attaching Texture Images to a Framebuffer"
|
|
*/
|
|
let currently_bound_framebuffer =
|
|
self.upcast::<WebGLObject>().context().bound_framebuffer();
|
|
if let Some(fb) = currently_bound_framebuffer {
|
|
fb.detach_texture(self);
|
|
}
|
|
|
|
context.send_command(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, 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),
|
|
};
|
|
|
|
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 => 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),
|
|
},
|
|
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 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_eq!(self.face_count.get(), 6);
|
|
|
|
let image_info = self.base_image_info();
|
|
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) -> ImageInfo {
|
|
assert!((self.base_mipmap_level as usize) < MAX_LEVEL_COUNT);
|
|
|
|
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<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
|
|
}
|
|
}
|