Add initial support for WebGL compressed textures

This commit is contained in:
Mátyás Mustoha 2019-03-25 12:50:45 +01:00 committed by Josh Matthews
parent a14b952fa3
commit 7f0b820d4e
16 changed files with 792 additions and 37 deletions

View file

@ -18,3 +18,5 @@ pub mod oestexturehalffloat;
pub mod oestexturehalffloatlinear;
pub mod oesvertexarrayobject;
pub mod webglcolorbufferfloat;
pub mod webglcompressedtextureetc1;
pub mod webglcompressedtextures3tc;

View file

@ -0,0 +1,58 @@
/* 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/. */
use super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions};
use crate::dom::bindings::codegen::Bindings::WEBGLCompressedTextureETC1Binding;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::webgltexture::{TexCompression, TexCompressionValidation};
use canvas_traits::webgl::{TexFormat, WebGLVersion};
use dom_struct::dom_struct;
#[dom_struct]
pub struct WEBGLCompressedTextureETC1 {
reflector_: Reflector,
}
impl WEBGLCompressedTextureETC1 {
fn new_inherited() -> WEBGLCompressedTextureETC1 {
Self {
reflector_: Reflector::new(),
}
}
}
impl WebGLExtension for WEBGLCompressedTextureETC1 {
type Extension = WEBGLCompressedTextureETC1;
fn new(ctx: &WebGLRenderingContext) -> DomRoot<WEBGLCompressedTextureETC1> {
reflect_dom_object(
Box::new(WEBGLCompressedTextureETC1::new_inherited()),
&*ctx.global(),
WEBGLCompressedTextureETC1Binding::Wrap,
)
}
fn spec() -> WebGLExtensionSpec {
WebGLExtensionSpec::Specific(WebGLVersion::WebGL1)
}
fn is_supported(ext: &WebGLExtensions) -> bool {
ext.supports_gl_extension("GL_OES_compressed_ETC1_RGB8_texture")
}
fn enable(ext: &WebGLExtensions) {
ext.add_tex_compression_formats(&[TexCompression {
format: TexFormat::CompressedRgbEtc1,
bytes_per_block: 8,
block_width: 4,
block_height: 4,
validation: TexCompressionValidation::None,
}]);
}
fn name() -> &'static str {
"WEBGL_compressed_texture_etc1"
}
}

View file

@ -0,0 +1,86 @@
/* 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/. */
use super::{WebGLExtension, WebGLExtensionSpec, WebGLExtensions};
use crate::dom::bindings::codegen::Bindings::WEBGLCompressedTextureS3TCBinding;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::webgltexture::{TexCompression, TexCompressionValidation};
use canvas_traits::webgl::{TexFormat, WebGLVersion};
use dom_struct::dom_struct;
#[dom_struct]
pub struct WEBGLCompressedTextureS3TC {
reflector_: Reflector,
}
impl WEBGLCompressedTextureS3TC {
fn new_inherited() -> WEBGLCompressedTextureS3TC {
Self {
reflector_: Reflector::new(),
}
}
}
impl WebGLExtension for WEBGLCompressedTextureS3TC {
type Extension = WEBGLCompressedTextureS3TC;
fn new(ctx: &WebGLRenderingContext) -> DomRoot<WEBGLCompressedTextureS3TC> {
reflect_dom_object(
Box::new(WEBGLCompressedTextureS3TC::new_inherited()),
&*ctx.global(),
WEBGLCompressedTextureS3TCBinding::Wrap,
)
}
fn spec() -> WebGLExtensionSpec {
WebGLExtensionSpec::Specific(WebGLVersion::WebGL1)
}
fn is_supported(ext: &WebGLExtensions) -> bool {
ext.supports_gl_extension("GL_EXT_texture_compression_s3tc") ||
ext.supports_all_gl_extension(&[
"GL_EXT_texture_compression_dxt1",
"GL_ANGLE_texture_compression_dxt3",
"GL_ANGLE_texture_compression_dxt5",
])
}
fn enable(ext: &WebGLExtensions) {
ext.add_tex_compression_formats(&[
TexCompression {
format: TexFormat::CompressedRgbS3tcDxt1,
bytes_per_block: 8,
block_width: 4,
block_height: 4,
validation: TexCompressionValidation::S3TC,
},
TexCompression {
format: TexFormat::CompressedRgbaS3tcDxt1,
bytes_per_block: 8,
block_width: 4,
block_height: 4,
validation: TexCompressionValidation::S3TC,
},
TexCompression {
format: TexFormat::CompressedRgbaS3tcDxt3,
bytes_per_block: 16,
block_width: 4,
block_height: 4,
validation: TexCompressionValidation::S3TC,
},
TexCompression {
format: TexFormat::CompressedRgbaS3tcDxt5,
bytes_per_block: 16,
block_width: 4,
block_height: 4,
validation: TexCompressionValidation::S3TC,
},
]);
}
fn name() -> &'static str {
"WEBGL_compressed_texture_s3tc"
}
}

View file

@ -17,6 +17,7 @@ use crate::dom::oestexturefloat::OESTextureFloat;
use crate::dom::oestexturehalffloat::OESTextureHalfFloat;
use crate::dom::webglcolorbufferfloat::WEBGLColorBufferFloat;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::webgltexture::TexCompression;
use canvas_traits::webgl::WebGLVersion;
use fnv::{FnvHashMap, FnvHashSet};
use gleam::gl::{self, GLenum};
@ -82,6 +83,8 @@ struct WebGLExtensionFeatures {
element_index_uint_enabled: bool,
/// WebGL EXT_blend_minmax extension.
blend_minmax_enabled: bool,
/// WebGL supported texture compression formats enabled by extensions.
tex_compression_formats: FnvHashMap<GLenum, TexCompression>,
}
impl WebGLExtensionFeatures {
@ -131,6 +134,7 @@ impl WebGLExtensionFeatures {
disabled_get_vertex_attrib_names,
element_index_uint_enabled,
blend_minmax_enabled,
tex_compression_formats: Default::default(),
}
}
}
@ -225,6 +229,13 @@ impl WebGLExtensions {
.any(|name| features.gl_extensions.contains(*name))
}
pub fn supports_all_gl_extension(&self, names: &[&str]) -> bool {
let features = self.features.borrow();
names
.iter()
.all(|name| features.gl_extensions.contains(*name))
}
pub fn enable_tex_type(&self, data_type: GLenum) {
self.features
.borrow_mut()
@ -335,6 +346,35 @@ impl WebGLExtensions {
.contains(&name)
}
pub fn add_tex_compression_formats(&self, formats: &[TexCompression]) {
let formats: FnvHashMap<GLenum, TexCompression> = formats
.iter()
.map(|&compression| (compression.format.as_gl_constant(), compression))
.collect();
self.features
.borrow_mut()
.tex_compression_formats
.extend(formats.iter());
}
pub fn get_tex_compression_format(&self, format_id: GLenum) -> Option<TexCompression> {
self.features
.borrow()
.tex_compression_formats
.get(&format_id)
.cloned()
}
pub fn get_tex_compression_ids(&self) -> Vec<GLenum> {
self.features
.borrow()
.tex_compression_formats
.keys()
.map(|&k| k)
.collect()
}
fn register_all_extensions(&self) {
self.register::<ext::angleinstancedarrays::ANGLEInstancedArrays>();
self.register::<ext::extblendminmax::EXTBlendMinmax>();
@ -349,6 +389,8 @@ impl WebGLExtensions {
self.register::<ext::oestexturehalffloatlinear::OESTextureHalfFloatLinear>();
self.register::<ext::oesvertexarrayobject::OESVertexArrayObject>();
self.register::<ext::webglcolorbufferfloat::WEBGLColorBufferFloat>();
self.register::<ext::webglcompressedtextureetc1::WEBGLCompressedTextureETC1>();
self.register::<ext::webglcompressedtextures3tc::WEBGLCompressedTextureS3TC>();
}
pub fn enable_element_index_uint(&self) {

View file

@ -6,7 +6,8 @@ use super::types::TexImageTarget;
use super::WebGLValidator;
use crate::dom::bindings::root::DomRoot;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::webgltexture::WebGLTexture;
use crate::dom::webgltexture::{ImageInfo, WebGLTexture};
use crate::dom::webgltexture::{TexCompression, TexCompressionValidation};
use canvas_traits::webgl::{TexDataType, TexFormat, WebGLError::*};
use std::{self, fmt};
@ -40,6 +41,10 @@ pub enum TexImageValidationError {
InvalidBorder,
/// Expected a power of two texture.
NonPotTexture,
/// Unrecognized texture compression format.
InvalidCompressionFormat,
/// Invalid X/Y texture offset parameters.
InvalidOffsets,
}
impl std::error::Error for TexImageValidationError {
@ -61,6 +66,8 @@ impl std::error::Error for TexImageValidationError {
InvalidTypeForFormat => "Invalid type for the given format",
InvalidBorder => "Invalid border",
NonPotTexture => "Expected a power of two texture",
InvalidCompressionFormat => "Unrecognized texture compression format",
InvalidOffsets => "Invalid X/Y texture offset parameters",
}
}
}
@ -357,3 +364,303 @@ impl<'a> WebGLValidator for TexImage2DValidator<'a> {
})
}
}
pub struct CommonCompressedTexImage2DValidator<'a> {
common_validator: CommonTexImage2DValidator<'a>,
data_len: usize,
}
impl<'a> CommonCompressedTexImage2DValidator<'a> {
pub fn new(
context: &'a WebGLRenderingContext,
target: u32,
level: i32,
width: i32,
height: i32,
border: i32,
compression_format: u32,
data_len: usize,
) -> Self {
CommonCompressedTexImage2DValidator {
common_validator: CommonTexImage2DValidator::new(
context,
target,
level,
compression_format,
width,
height,
border,
),
data_len,
}
}
}
pub struct CommonCompressedTexImage2DValidatorResult {
pub texture: DomRoot<WebGLTexture>,
pub target: TexImageTarget,
pub level: u32,
pub width: u32,
pub height: u32,
pub compression: TexCompression,
}
fn valid_s3tc_dimension(level: u32, side_length: u32, block_size: u32) -> bool {
(side_length % block_size == 0) || (level > 0 && [0, 1, 2].contains(&side_length))
}
fn valid_compressed_data_len(
data_len: usize,
width: u32,
height: u32,
compression: &TexCompression,
) -> bool {
let block_width = compression.block_width as u32;
let block_height = compression.block_height as u32;
let required_blocks_hor = (width + block_width - 1) / block_width;
let required_blocks_ver = (height + block_height - 1) / block_height;
let required_blocks = required_blocks_hor * required_blocks_ver;
let required_bytes = required_blocks * compression.bytes_per_block as u32;
data_len == required_bytes as usize
}
fn is_subimage_blockaligned(
xoffset: u32,
yoffset: u32,
width: u32,
height: u32,
compression: &TexCompression,
tex_info: &ImageInfo,
) -> bool {
let block_width = compression.block_width as u32;
let block_height = compression.block_height as u32;
(xoffset % block_width == 0 && yoffset % block_height == 0) &&
(width % block_width == 0 || xoffset + width == tex_info.width()) &&
(height % block_height == 0 || yoffset + height == tex_info.height())
}
impl<'a> WebGLValidator for CommonCompressedTexImage2DValidator<'a> {
type Error = TexImageValidationError;
type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
let context = self.common_validator.context;
let CommonTexImage2DValidatorResult {
texture,
target,
level,
internal_format,
width,
height,
border: _,
} = self.common_validator.validate()?;
// GL_INVALID_ENUM is generated if internalformat is not a supported
// format returned in GL_COMPRESSED_TEXTURE_FORMATS.
let compression = context
.extension_manager()
.get_tex_compression_format(internal_format.as_gl_constant());
let compression = match compression {
Some(compression) => compression,
None => {
context.webgl_error(InvalidEnum);
return Err(TexImageValidationError::InvalidCompressionFormat);
},
};
// GL_INVALID_VALUE is generated if imageSize is not consistent with the
// format, dimensions, and contents of the specified compressed image data.
if !valid_compressed_data_len(self.data_len, width, height, &compression) {
context.webgl_error(InvalidValue);
return Err(TexImageValidationError::TextureFormatMismatch);
}
Ok(CommonCompressedTexImage2DValidatorResult {
texture,
target,
level,
width,
height,
compression,
})
}
}
pub struct CompressedTexImage2DValidator<'a> {
compression_validator: CommonCompressedTexImage2DValidator<'a>,
}
impl<'a> CompressedTexImage2DValidator<'a> {
pub fn new(
context: &'a WebGLRenderingContext,
target: u32,
level: i32,
width: i32,
height: i32,
border: i32,
compression_format: u32,
data_len: usize,
) -> Self {
CompressedTexImage2DValidator {
compression_validator: CommonCompressedTexImage2DValidator::new(
context,
target,
level,
width,
height,
border,
compression_format,
data_len,
),
}
}
}
impl<'a> WebGLValidator for CompressedTexImage2DValidator<'a> {
type Error = TexImageValidationError;
type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
let context = self.compression_validator.common_validator.context;
let CommonCompressedTexImage2DValidatorResult {
texture,
target,
level,
width,
height,
compression,
} = self.compression_validator.validate()?;
// GL_INVALID_OPERATION is generated if parameter combinations are not
// supported by the specific compressed internal format as specified
// in the specific texture compression extension.
let compression_valid = match compression.validation {
TexCompressionValidation::S3TC => {
let valid_width =
valid_s3tc_dimension(level, width, compression.block_width as u32);
let valid_height =
valid_s3tc_dimension(level, height, compression.block_height as u32);
valid_width && valid_height
},
TexCompressionValidation::None => true,
};
if !compression_valid {
context.webgl_error(InvalidOperation);
return Err(TexImageValidationError::TextureFormatMismatch);
}
Ok(CommonCompressedTexImage2DValidatorResult {
texture,
target,
level,
width,
height,
compression,
})
}
}
pub struct CompressedTexSubImage2DValidator<'a> {
compression_validator: CommonCompressedTexImage2DValidator<'a>,
xoffset: i32,
yoffset: i32,
}
impl<'a> CompressedTexSubImage2DValidator<'a> {
pub fn new(
context: &'a WebGLRenderingContext,
target: u32,
level: i32,
xoffset: i32,
yoffset: i32,
width: i32,
height: i32,
compression_format: u32,
data_len: usize,
) -> Self {
CompressedTexSubImage2DValidator {
compression_validator: CommonCompressedTexImage2DValidator::new(
context,
target,
level,
width,
height,
0,
compression_format,
data_len,
),
xoffset,
yoffset,
}
}
}
impl<'a> WebGLValidator for CompressedTexSubImage2DValidator<'a> {
type Error = TexImageValidationError;
type ValidatedOutput = CommonCompressedTexImage2DValidatorResult;
fn validate(self) -> Result<Self::ValidatedOutput, TexImageValidationError> {
let context = self.compression_validator.common_validator.context;
let CommonCompressedTexImage2DValidatorResult {
texture,
target,
level,
width,
height,
compression,
} = self.compression_validator.validate()?;
let tex_info = texture.image_info_for_target(&target, level);
// GL_INVALID_VALUE is generated if:
// - xoffset or yoffset is less than 0
// - x offset plus the width is greater than the texture width
// - y offset plus the height is greater than the texture height
if self.xoffset < 0 ||
(self.xoffset as u32 + width) > tex_info.width() ||
self.yoffset < 0 ||
(self.yoffset as u32 + height) > tex_info.height()
{
context.webgl_error(InvalidValue);
return Err(TexImageValidationError::InvalidOffsets);
}
// GL_INVALID_OPERATION is generated if format does not match
// internal_format.
if compression.format != tex_info.internal_format().unwrap() {
context.webgl_error(InvalidOperation);
return Err(TexImageValidationError::TextureFormatMismatch);
}
// GL_INVALID_OPERATION is generated if parameter combinations are not
// supported by the specific compressed internal format as specified
// in the specific texture compression extension.
let compression_valid = match compression.validation {
TexCompressionValidation::S3TC => is_subimage_blockaligned(
self.xoffset as u32,
self.yoffset as u32,
width,
height,
&compression,
&tex_info,
),
TexCompressionValidation::None => true,
};
if !compression_valid {
context.webgl_error(InvalidOperation);
return Err(TexImageValidationError::TextureFormatMismatch);
}
Ok(CommonCompressedTexImage2DValidatorResult {
texture,
target,
level,
width,
height,
compression,
})
}
}

View file

@ -26,9 +26,10 @@ use crate::dom::htmliframeelement::HTMLIFrameElement;
use crate::dom::node::{document_from_node, window_from_node, Node, NodeDamage};
use crate::dom::webgl_extensions::WebGLExtensions;
use crate::dom::webgl_validations::tex_image_2d::{
CommonTexImage2DValidator, CommonTexImage2DValidatorResult,
CommonCompressedTexImage2DValidatorResult, CommonTexImage2DValidator,
CommonTexImage2DValidatorResult, CompressedTexImage2DValidator,
CompressedTexSubImage2DValidator, TexImage2DValidator, TexImage2DValidatorResult,
};
use crate::dom::webgl_validations::tex_image_2d::{TexImage2DValidator, TexImage2DValidatorResult};
use crate::dom::webgl_validations::types::TexImageTarget;
use crate::dom::webgl_validations::WebGLValidator;
use crate::dom::webglactiveinfo::WebGLActiveInfo;
@ -1227,9 +1228,11 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return Int32Value(constants::UNSIGNED_BYTE as i32);
},
constants::COMPRESSED_TEXTURE_FORMATS => {
// FIXME(nox): https://github.com/servo/servo/issues/20594
let format_ids = self.extension_manager.get_tex_compression_ids();
rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>());
let _ = Uint32Array::create(cx, CreateWith::Slice(&[]), rval.handle_mut()).unwrap();
let _ = Uint32Array::create(cx, CreateWith::Slice(&format_ids), rval.handle_mut())
.unwrap();
return ObjectValue(rval.get());
},
constants::VERSION => {
@ -1756,36 +1759,116 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
#[allow(unsafe_code)]
fn CompressedTexImage2D(
&self,
_target: u32,
_level: i32,
_internal_format: u32,
_width: i32,
_height: i32,
_border: i32,
_data: CustomAutoRooterGuard<ArrayBufferView>,
target: u32,
level: i32,
internal_format: u32,
width: i32,
height: i32,
border: i32,
data: CustomAutoRooterGuard<ArrayBufferView>,
) {
// FIXME: No compressed texture format is currently supported, so error out as per
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#COMPRESSED_TEXTURE_SUPPORT
self.webgl_error(InvalidEnum);
let validator = CompressedTexImage2DValidator::new(
self,
target,
level,
width,
height,
border,
internal_format,
data.len(),
);
let CommonCompressedTexImage2DValidatorResult {
texture,
target,
level,
width,
height,
compression,
} = match validator.validate() {
Ok(result) => result,
Err(_) => return,
};
let buff = IpcSharedMemory::from_bytes(unsafe { data.as_slice() });
let pixels = TexPixels::from_array(buff, Size2D::new(width, height));
handle_potential_webgl_error!(
self,
texture.initialize(
target,
pixels.size.width,
pixels.size.height,
1,
compression.format,
level,
Some(TexDataType::UnsignedByte)
)
);
self.send_command(WebGLCommand::CompressedTexImage2D {
target: target.as_gl_constant(),
level,
internal_format,
size: Size2D::new(width, height),
data: pixels.data.into(),
});
if let Some(fb) = self.bound_framebuffer.get() {
fb.invalidate_texture(&*texture);
}
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
#[allow(unsafe_code)]
fn CompressedTexSubImage2D(
&self,
_target: u32,
_level: i32,
_xoffset: i32,
_yoffset: i32,
_width: i32,
_height: i32,
_format: u32,
_data: CustomAutoRooterGuard<ArrayBufferView>,
target: u32,
level: i32,
xoffset: i32,
yoffset: i32,
width: i32,
height: i32,
format: u32,
data: CustomAutoRooterGuard<ArrayBufferView>,
) {
// FIXME: No compressed texture format is currently supported, so error out as per
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#COMPRESSED_TEXTURE_SUPPORT
self.webgl_error(InvalidEnum);
let validator = CompressedTexSubImage2DValidator::new(
self,
target,
level,
xoffset,
yoffset,
width,
height,
format,
data.len(),
);
let CommonCompressedTexImage2DValidatorResult {
texture: _,
target,
level,
width,
height,
..
} = match validator.validate() {
Ok(result) => result,
Err(_) => return,
};
let buff = IpcSharedMemory::from_bytes(unsafe { data.as_slice() });
let pixels = TexPixels::from_array(buff, Size2D::new(width, height));
self.send_command(WebGLCommand::CompressedTexSubImage2D {
target: target.as_gl_constant(),
level: level as i32,
xoffset,
yoffset,
size: Size2D::new(width, height),
format,
data: pixels.data.into(),
});
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8

View file

@ -470,7 +470,24 @@ impl ImageInfo {
}
fn is_compressed_format(&self) -> bool {
// TODO: Once Servo supports compressed formats, check for them here
false
match self.internal_format {
Some(format) => format.is_compressed(),
None => false,
}
}
}
#[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,
}

View file

@ -0,0 +1,13 @@
/* 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/. */
/*
* WebGL IDL definitions from the Khronos specification:
* https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
*/
[NoInterfaceObject]
interface WEBGLCompressedTextureETC1 {
/* Compressed Texture Format */
const GLenum COMPRESSED_RGB_ETC1_WEBGL = 0x8D64;
}; // interface WEBGLCompressedTextureETC1

View file

@ -0,0 +1,16 @@
/* 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/. */
/*
* WebGL IDL definitions from the Khronos specification:
* https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
*/
[NoInterfaceObject]
interface WEBGLCompressedTextureS3TC {
/* Compressed Texture Formats */
const GLenum COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
const GLenum COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
const GLenum COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
const GLenum COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
}; // interface WEBGLCompressedTextureS3TC