diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2019-05-21 17:10:24 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-21 17:10:24 -0400 |
commit | 123f58592c9bedae735a84fe5c93b0a20292ea86 (patch) | |
tree | 88a191c94b97aff6cf47758b3113266387f9c127 | |
parent | cde3ecf64091e56d761697d0a798da5b2d2ca352 (diff) | |
parent | 7f0b820d4ee87351aba9e7c785a704c6c83f135f (diff) | |
download | servo-123f58592c9bedae735a84fe5c93b0a20292ea86.tar.gz servo-123f58592c9bedae735a84fe5c93b0a20292ea86.zip |
Auto merge of #23226 - mmatyas:webgl_compressed_textures, r=jdm
Add initial support for WebGL compressed textures
This patch is an initial implementation of WebGL compressed texture support, it contains
- functions for registering and querying compressed texture extensions
- initial implementation of `CompressedTexImage2D` and `CompressedTexSubImage2D` and their parameter validation
- implementation of S3TC (DXT1, DXT3, DXT5) and ETC1 extensions as examples
What's still missing:
- some of the parameter validation steps are missing
- the pixel comparison tests fail for more complex cases (I'm probably missing something trivial at the GL calls)
Related: #10209 and #20594
cc @jdm @zakorgy
---
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] Related issues: #10209, #20594
- [x] There are tests for these changes
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23226)
<!-- Reviewable:end -->
16 files changed, 792 insertions, 37 deletions
diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs index 175a061cbf4..cec8cc929d6 100644 --- a/components/canvas/webgl_thread.rs +++ b/components/canvas/webgl_thread.rs @@ -1168,6 +1168,43 @@ impl WebGLImpl { &pixels, ); }, + WebGLCommand::CompressedTexImage2D { + target, + level, + internal_format, + size, + ref data, + } => { + ctx.gl().compressed_tex_image_2d( + target, + level as i32, + internal_format, + size.width as i32, + size.height as i32, + 0, + &*data, + ); + }, + WebGLCommand::CompressedTexSubImage2D { + target, + level, + xoffset, + yoffset, + size, + format, + ref data, + } => { + ctx.gl().compressed_tex_sub_image_2d( + target, + level as i32, + xoffset as i32, + yoffset as i32, + size.width as i32, + size.height as i32, + format, + &*data, + ); + }, WebGLCommand::DrawingBufferWidth(ref sender) => sender .send(ctx.borrow_draw_buffer().unwrap().size().width) .unwrap(), diff --git a/components/canvas_traits/webgl.rs b/components/canvas_traits/webgl.rs index af88ca905aa..a36e8d0e659 100644 --- a/components/canvas_traits/webgl.rs +++ b/components/canvas_traits/webgl.rs @@ -331,6 +331,22 @@ pub enum WebGLCommand { pixel_format: Option<PixelFormat>, data: TruncatedDebug<IpcSharedMemory>, }, + CompressedTexImage2D { + target: u32, + level: u32, + internal_format: u32, + size: Size2D<u32>, + data: TruncatedDebug<IpcSharedMemory>, + }, + CompressedTexSubImage2D { + target: u32, + level: i32, + xoffset: i32, + yoffset: i32, + size: Size2D<u32>, + format: u32, + data: TruncatedDebug<IpcSharedMemory>, + }, DrawingBufferWidth(WebGLSender<i32>), DrawingBufferHeight(WebGLSender<i32>), Finish(WebGLSender<()>), @@ -740,6 +756,25 @@ macro_rules! gl_enums { } } +// FIXME: These should come from gleam +mod gl_ext_constants { + use gleam::gl::types::GLenum; + + pub const COMPRESSED_RGB_S3TC_DXT1_EXT: GLenum = 0x83F0; + pub const COMPRESSED_RGBA_S3TC_DXT1_EXT: GLenum = 0x83F1; + pub const COMPRESSED_RGBA_S3TC_DXT3_EXT: GLenum = 0x83F2; + pub const COMPRESSED_RGBA_S3TC_DXT5_EXT: GLenum = 0x83F3; + pub const COMPRESSED_RGB_ETC1_WEBGL: GLenum = 0x8D64; + + pub static COMPRESSIONS: &'static [GLenum] = &[ + COMPRESSED_RGB_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT3_EXT, + COMPRESSED_RGBA_S3TC_DXT5_EXT, + COMPRESSED_RGB_ETC1_WEBGL, + ]; +} + gl_enums! { pub enum TexFormat { DepthComponent = gl::DEPTH_COMPONENT, @@ -748,6 +783,11 @@ gl_enums! { RGBA = gl::RGBA, Luminance = gl::LUMINANCE, LuminanceAlpha = gl::LUMINANCE_ALPHA, + CompressedRgbS3tcDxt1 = gl_ext_constants::COMPRESSED_RGB_S3TC_DXT1_EXT, + CompressedRgbaS3tcDxt1 = gl_ext_constants::COMPRESSED_RGBA_S3TC_DXT1_EXT, + CompressedRgbaS3tcDxt3 = gl_ext_constants::COMPRESSED_RGBA_S3TC_DXT3_EXT, + CompressedRgbaS3tcDxt5 = gl_ext_constants::COMPRESSED_RGBA_S3TC_DXT5_EXT, + CompressedRgbEtc1 = gl_ext_constants::COMPRESSED_RGB_ETC1_WEBGL, } pub enum TexDataType { @@ -771,8 +811,14 @@ impl TexFormat { TexFormat::LuminanceAlpha => 2, TexFormat::RGB => 3, TexFormat::RGBA => 4, + _ => 1, } } + + /// Returns whether this format is a known texture compression format. + pub fn is_compressed(&self) -> bool { + gl_ext_constants::COMPRESSIONS.contains(&self.as_gl_constant()) + } } impl TexDataType { diff --git a/components/script/dom/webgl_extensions/ext/mod.rs b/components/script/dom/webgl_extensions/ext/mod.rs index 888e977c68e..ccb768cc04f 100644 --- a/components/script/dom/webgl_extensions/ext/mod.rs +++ b/components/script/dom/webgl_extensions/ext/mod.rs @@ -18,3 +18,5 @@ pub mod oestexturehalffloat; pub mod oestexturehalffloatlinear; pub mod oesvertexarrayobject; pub mod webglcolorbufferfloat; +pub mod webglcompressedtextureetc1; +pub mod webglcompressedtextures3tc; diff --git a/components/script/dom/webgl_extensions/ext/webglcompressedtextureetc1.rs b/components/script/dom/webgl_extensions/ext/webglcompressedtextureetc1.rs new file mode 100644 index 00000000000..29805fd4bdd --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/webglcompressedtextureetc1.rs @@ -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" + } +} diff --git a/components/script/dom/webgl_extensions/ext/webglcompressedtextures3tc.rs b/components/script/dom/webgl_extensions/ext/webglcompressedtextures3tc.rs new file mode 100644 index 00000000000..0e4385abd6b --- /dev/null +++ b/components/script/dom/webgl_extensions/ext/webglcompressedtextures3tc.rs @@ -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" + } +} diff --git a/components/script/dom/webgl_extensions/extensions.rs b/components/script/dom/webgl_extensions/extensions.rs index aa6d128ae89..e71fcf7dac5 100644 --- a/components/script/dom/webgl_extensions/extensions.rs +++ b/components/script/dom/webgl_extensions/extensions.rs @@ -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) { diff --git a/components/script/dom/webgl_validations/tex_image_2d.rs b/components/script/dom/webgl_validations/tex_image_2d.rs index d9bce435234..329ffd10349 100644 --- a/components/script/dom/webgl_validations/tex_image_2d.rs +++ b/components/script/dom/webgl_validations/tex_image_2d.rs @@ -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, + }) + } +} diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 3100fbf942d..0ccc2a53825 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -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 diff --git a/components/script/dom/webgltexture.rs b/components/script/dom/webgltexture.rs index 261edbe67be..8982e4bca0c 100644 --- a/components/script/dom/webgltexture.rs +++ b/components/script/dom/webgltexture.rs @@ -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, +} diff --git a/components/script/dom/webidls/WEBGLCompressedTextureETC1.webidl b/components/script/dom/webidls/WEBGLCompressedTextureETC1.webidl new file mode 100644 index 00000000000..c8ba921764d --- /dev/null +++ b/components/script/dom/webidls/WEBGLCompressedTextureETC1.webidl @@ -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 diff --git a/components/script/dom/webidls/WEBGLCompressedTextureS3TC.webidl b/components/script/dom/webidls/WEBGLCompressedTextureS3TC.webidl new file mode 100644 index 00000000000..0da53b81c17 --- /dev/null +++ b/components/script/dom/webidls/WEBGLCompressedTextureS3TC.webidl @@ -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 diff --git a/tests/wpt/webgl/meta/MANIFEST.json b/tests/wpt/webgl/meta/MANIFEST.json index ccf761ac8a9..752c28611b4 100644 --- a/tests/wpt/webgl/meta/MANIFEST.json +++ b/tests/wpt/webgl/meta/MANIFEST.json @@ -10888,7 +10888,9 @@ "conformance/extensions/webgl-compressed-texture-s3tc.html": [ [ "conformance/extensions/webgl-compressed-texture-s3tc.html", - {} + { + "timeout": "long" + } ] ], "conformance/extensions/webgl-compressed-texture-size-limit.html": [ @@ -27817,7 +27819,7 @@ "testharness" ], "conformance/extensions/webgl-compressed-texture-s3tc.html": [ - "28e3cfc5628760d1ae27166612f17ed46fa12b88", + "ea56180224af7537748a649a724563a86c431710", "testharness" ], "conformance/extensions/webgl-compressed-texture-size-limit.html": [ diff --git a/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html b/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html index 28e3cfc5628..ea56180224a 100644 --- a/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html +++ b/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html @@ -29,6 +29,7 @@ <html> <head> <meta charset="utf-8"> +<meta name="timeout" content="long"> <link rel="stylesheet" href="../../resources/js-test-style.css"/> <script src=/resources/testharness.js></script> <script src=/resources/testharnessreport.js></script> @@ -694,13 +695,6 @@ function compareRect(width, height, channels, expectedData, filteringMode) { gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, actual); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "reading back pixels"); - var div = document.createElement("div"); - div.className = "testimages"; - insertImg(div, "expected", makeImage(width, height, expectedData, channels == 4)); - insertImg(div, "actual", makeImage(width, height, actual, channels == 4)); - div.appendChild(document.createElement('br')); - document.getElementById("console").appendChild(div); - var failed = false; for (var yy = 0; yy < height; ++yy) { for (var xx = 0; xx < width; ++xx) { @@ -714,6 +708,13 @@ function compareRect(width, height, channels, expectedData, filteringMode) { for (var jj = 0; jj < 4; ++jj) { if (actual[offset + jj] != expected[jj]) { failed = true; + var div = document.createElement("div"); + div.className = "testimages"; + insertImg(div, "expected", makeImage(width, height, expectedData, channels == 4)); + insertImg(div, "actual", makeImage(width, height, actual, channels == 4)); + div.appendChild(document.createElement('br')); + document.getElementById("console").appendChild(div); + var was = actual[offset + 0].toString(); for (var j = 1; j < 4; ++j) { was += "," + actual[offset + j]; diff --git a/tests/wpt/webgl/tools/compressed-images.patch b/tests/wpt/webgl/tools/compressed-images.patch new file mode 100644 index 00000000000..d403b7d7cf5 --- /dev/null +++ b/tests/wpt/webgl/tools/compressed-images.patch @@ -0,0 +1,32 @@ +diff --git a/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html b/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html +index 308e8577a8..ea56180224 100644 +--- a/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html ++++ b/tests/wpt/webgl/tests/conformance/extensions/webgl-compressed-texture-s3tc.html +@@ -695,13 +695,6 @@ function compareRect(width, height, channels, expectedData, filteringMode) { + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, actual); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "reading back pixels"); + +- var div = document.createElement("div"); +- div.className = "testimages"; +- insertImg(div, "expected", makeImage(width, height, expectedData, channels == 4)); +- insertImg(div, "actual", makeImage(width, height, actual, channels == 4)); +- div.appendChild(document.createElement('br')); +- document.getElementById("console").appendChild(div); +- + var failed = false; + for (var yy = 0; yy < height; ++yy) { + for (var xx = 0; xx < width; ++xx) { +@@ -715,6 +708,13 @@ function compareRect(width, height, channels, expectedData, filteringMode) { + for (var jj = 0; jj < 4; ++jj) { + if (actual[offset + jj] != expected[jj]) { + failed = true; ++ var div = document.createElement("div"); ++ div.className = "testimages"; ++ insertImg(div, "expected", makeImage(width, height, expectedData, channels == 4)); ++ insertImg(div, "actual", makeImage(width, height, actual, channels == 4)); ++ div.appendChild(document.createElement('br')); ++ document.getElementById("console").appendChild(div); ++ + var was = actual[offset + 0].toString(); + for (var j = 1; j < 4; ++j) { + was += "," + actual[offset + j]; diff --git a/tests/wpt/webgl/tools/import-conformance-tests.py b/tests/wpt/webgl/tools/import-conformance-tests.py index 58afe1ddc77..9440abab4d0 100755 --- a/tests/wpt/webgl/tools/import-conformance-tests.py +++ b/tests/wpt/webgl/tools/import-conformance-tests.py @@ -15,6 +15,7 @@ PATCHES = [ ("unit.patch", "conformance/more/unit.js"), ("timeout.patch", None), ("set-zero-timeout.patch", "js/webgl-test-utils.js"), + ("compressed-images.patch", "conformance/extensions/webgl-compressed-texture-s3tc.html"), ] # Fix for 'UnicodeDecodeError: 'ascii' codec can't decode byte' diff --git a/tests/wpt/webgl/tools/timeout.patch b/tests/wpt/webgl/tools/timeout.patch index b5284094e52..89bf21807bc 100644 --- a/tests/wpt/webgl/tools/timeout.patch +++ b/tests/wpt/webgl/tools/timeout.patch @@ -92,3 +92,15 @@ index c1542f4fa9..b3ee786e0b 100644 <title>WebGL uniform packing restrctions Conformance Test</title> <link rel="stylesheet" href="../../../resources/js-test-style.css"/> <link rel="stylesheet" href="../../../resources/glsl-feature-tests.css"/> +diff --git i/conformance/extensions/webgl-compressed-texture-s3tc.html w/conformance/extensions/webgl-compressed-texture-s3tc.html +index 28e3cfc562..308e8577a8 100644 +--- i/conformance/extensions/webgl-compressed-texture-s3tc.html ++++ w/conformance/extensions/webgl-compressed-texture-s3tc.html +@@ -29,6 +29,7 @@ + <html> + <head> + <meta charset="utf-8"> ++<meta name="timeout" content="long"> + <link rel="stylesheet" href="../../resources/js-test-style.css"/> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> |