зеркало из https://github.com/mozilla/gecko-dev.git
servo: Merge #15122 - webgl: premultiplication, y flipping, and format conversion (from anholt:webgl-premultiply); r=emilio
<!-- Please describe your changes on the following line: --> This series implements a bunch of the texture unpack path features for WebGL. There are known issues with big-endian systems noted in the comments, but I don't know of a clean way to cast from `Vec<u16>` to `Vec<u8>` (which, if we had one, would make this code much cleaner anyway). --- <!-- 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 _____ <!-- 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: 536c0d74994bf8b5eaef3f64247753d75ba7baab --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : c7451e83a882c0e67e80c480b240d0761e2ba106
This commit is contained in:
Родитель
355086036f
Коммит
99d6112182
|
@ -2273,6 +2273,7 @@ dependencies = [
|
|||
"audio-video-metadata 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bluetooth_traits 0.0.1",
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"canvas_traits 0.0.1",
|
||||
"caseless 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cmake 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -417,6 +417,10 @@ pub fn byte_swap(data: &mut [u8]) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn multiply_u8_pixel(a: u8, b: u8) -> u8 {
|
||||
return (a as u32 * b as u32 / 255) as u8;
|
||||
}
|
||||
|
||||
pub fn byte_swap_and_premultiply(data: &mut [u8]) {
|
||||
let length = data.len();
|
||||
|
||||
|
@ -427,9 +431,9 @@ pub fn byte_swap_and_premultiply(data: &mut [u8]) {
|
|||
let b = data[i + 0];
|
||||
let a = data[i + 3];
|
||||
|
||||
data[i + 0] = ((r as u32) * (a as u32) / 255) as u8;
|
||||
data[i + 1] = ((g as u32) * (a as u32) / 255) as u8;
|
||||
data[i + 2] = ((b as u32) * (a as u32) / 255) as u8;
|
||||
data[i + 0] = multiply_u8_pixel(r, a);
|
||||
data[i + 1] = multiply_u8_pixel(g, a);
|
||||
data[i + 2] = multiply_u8_pixel(b, a);
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ audio-video-metadata = "0.1.2"
|
|||
atomic_refcell = "0.1"
|
||||
bitflags = "0.7"
|
||||
bluetooth_traits = {path = "../bluetooth_traits"}
|
||||
byteorder = "0.5"
|
||||
canvas_traits = {path = "../canvas_traits"}
|
||||
caseless = "0.1.0"
|
||||
cookie = {version = "0.2.5", features = ["serialize-rustc"]}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
* 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/. */
|
||||
|
||||
use canvas_traits::{CanvasCommonMsg, CanvasMsg, byte_swap};
|
||||
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
|
||||
use canvas_traits::{CanvasCommonMsg, CanvasMsg, byte_swap, multiply_u8_pixel};
|
||||
use core::nonzero::NonZero;
|
||||
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes};
|
||||
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
|
||||
|
@ -112,6 +113,7 @@ fn has_invalid_blend_constants(arg1: u32, arg2: u32) -> bool {
|
|||
(_, _) => false
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of bitflags for texture unpacking (texImage2d, etc...)
|
||||
bitflags! {
|
||||
#[derive(HeapSizeOf, JSTraceable)]
|
||||
|
@ -133,6 +135,7 @@ pub struct WebGLRenderingContext {
|
|||
#[ignore_heap_size_of = "Defined in webrender_traits"]
|
||||
last_error: Cell<Option<WebGLError>>,
|
||||
texture_unpacking_settings: Cell<TextureUnpacking>,
|
||||
texture_unpacking_alignment: Cell<u32>,
|
||||
bound_framebuffer: MutNullableJS<WebGLFramebuffer>,
|
||||
bound_renderbuffer: MutNullableJS<WebGLRenderbuffer>,
|
||||
bound_texture_2d: MutNullableJS<WebGLTexture>,
|
||||
|
@ -168,6 +171,7 @@ impl WebGLRenderingContext {
|
|||
canvas: JS::from_ref(canvas),
|
||||
last_error: Cell::new(None),
|
||||
texture_unpacking_settings: Cell::new(CONVERT_COLORSPACE),
|
||||
texture_unpacking_alignment: Cell::new(4),
|
||||
bound_framebuffer: MutNullableJS::new(None),
|
||||
bound_texture_2d: MutNullableJS::new(None),
|
||||
bound_texture_cube_map: MutNullableJS::new(None),
|
||||
|
@ -364,6 +368,67 @@ impl WebGLRenderingContext {
|
|||
true
|
||||
}
|
||||
|
||||
/// Translates an image in rgba8 (red in the first byte) format to
|
||||
/// the format that was requested of TexImage.
|
||||
///
|
||||
/// From the WebGL 1.0 spec, 5.14.8:
|
||||
///
|
||||
/// "The source image data is conceptually first converted to
|
||||
/// the data type and format specified by the format and type
|
||||
/// arguments, and then transferred to the WebGL
|
||||
/// implementation. If a packed pixel format is specified
|
||||
/// which would imply loss of bits of precision from the image
|
||||
/// data, this loss of precision must occur."
|
||||
fn rgba8_image_to_tex_image_data(&self,
|
||||
format: TexFormat,
|
||||
data_type: TexDataType,
|
||||
pixels: Vec<u8>) -> Vec<u8> {
|
||||
// hint for vector allocation sizing.
|
||||
let pixel_count = pixels.len() / 4;
|
||||
|
||||
match (format, data_type) {
|
||||
(TexFormat::RGBA, TexDataType::UnsignedByte) => pixels,
|
||||
(TexFormat::RGB, TexDataType::UnsignedByte) => pixels,
|
||||
|
||||
(TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
|
||||
let mut rgba4 = Vec::<u8>::with_capacity(pixel_count * 2);
|
||||
for rgba8 in pixels.chunks(4) {
|
||||
rgba4.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf0) << 8 |
|
||||
(rgba8[1] as u16 & 0xf0) << 4 |
|
||||
(rgba8[2] as u16 & 0xf0) |
|
||||
(rgba8[3] as u16 & 0xf0) >> 4).unwrap();
|
||||
}
|
||||
rgba4
|
||||
}
|
||||
|
||||
(TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
|
||||
let mut rgba5551 = Vec::<u8>::with_capacity(pixel_count * 2);
|
||||
for rgba8 in pixels.chunks(4) {
|
||||
rgba5551.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf8) << 8 |
|
||||
(rgba8[1] as u16 & 0xf8) << 3 |
|
||||
(rgba8[2] as u16 & 0xf8) >> 2 |
|
||||
(rgba8[3] as u16) >> 7).unwrap();
|
||||
}
|
||||
rgba5551
|
||||
}
|
||||
|
||||
(TexFormat::RGB, TexDataType::UnsignedShort565) => {
|
||||
let mut rgb565 = Vec::<u8>::with_capacity(pixel_count * 2);
|
||||
for rgba8 in pixels.chunks(4) {
|
||||
rgb565.write_u16::<NativeEndian>((rgba8[0] as u16 & 0xf8) << 8 |
|
||||
(rgba8[1] as u16 & 0xfc) << 3 |
|
||||
(rgba8[2] as u16 & 0xf8) >> 3).unwrap();
|
||||
}
|
||||
rgb565
|
||||
}
|
||||
|
||||
// Validation should have ensured that we only hit the
|
||||
// above cases, but we haven't turned the (format, type)
|
||||
// into an enum yet so there's a default case here.
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_image_pixels(&self,
|
||||
source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>)
|
||||
-> ImagePixelResult {
|
||||
|
@ -398,10 +463,7 @@ impl WebGLRenderingContext {
|
|||
|
||||
let size = Size2D::new(img.width as i32, img.height as i32);
|
||||
|
||||
// TODO(emilio): Validate that the format argument
|
||||
// is coherent with the image.
|
||||
//
|
||||
// RGB8 should be easy to support too
|
||||
// For now Servo's images are all stored as RGBA8 internally.
|
||||
let mut data = match img.format {
|
||||
PixelFormat::RGBA8 => img.bytes.to_vec(),
|
||||
_ => unimplemented!(),
|
||||
|
@ -436,6 +498,7 @@ impl WebGLRenderingContext {
|
|||
height: u32,
|
||||
format: TexFormat,
|
||||
data_type: TexDataType,
|
||||
unpacking_alignment: u32,
|
||||
data: *mut JSObject)
|
||||
-> Result<u32, ()> {
|
||||
let element_size = data_type.element_size();
|
||||
|
@ -467,8 +530,114 @@ impl WebGLRenderingContext {
|
|||
}
|
||||
|
||||
// NOTE: width and height are positive or zero due to validate()
|
||||
let expected_byte_length = width * height * element_size * components / components_per_element;
|
||||
return Ok(expected_byte_length);
|
||||
if height == 0 {
|
||||
return Ok(0);
|
||||
} else {
|
||||
// We need to be careful here to not count unpack
|
||||
// alignment at the end of the image, otherwise (for
|
||||
// example) passing a single byte for uploading a 1x1
|
||||
// GL_ALPHA/GL_UNSIGNED_BYTE texture would throw an error.
|
||||
let cpp = element_size * components / components_per_element;
|
||||
let stride = (width * cpp + unpacking_alignment - 1) & !(unpacking_alignment - 1);
|
||||
return Ok(stride * (height - 1) + width * cpp);
|
||||
}
|
||||
}
|
||||
|
||||
/// Flips the pixels in the Vec on the Y axis if
|
||||
/// UNPACK_FLIP_Y_WEBGL is currently enabled.
|
||||
fn flip_teximage_y(&self,
|
||||
pixels: Vec<u8>,
|
||||
internal_format: TexFormat,
|
||||
data_type: TexDataType,
|
||||
width: usize,
|
||||
height: usize,
|
||||
unpacking_alignment: usize) -> Vec<u8> {
|
||||
if !self.texture_unpacking_settings.get().contains(FLIP_Y_AXIS) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
let cpp = (data_type.element_size() *
|
||||
internal_format.components() / data_type.components_per_element()) as usize;
|
||||
|
||||
let stride = (width * cpp + unpacking_alignment - 1) & !(unpacking_alignment - 1);
|
||||
|
||||
let mut flipped = Vec::<u8>::with_capacity(pixels.len());
|
||||
|
||||
for y in 0..height {
|
||||
let flipped_y = height - 1 - y;
|
||||
let start = flipped_y * stride;
|
||||
|
||||
flipped.extend_from_slice(&pixels[start..(start + width * cpp)]);
|
||||
flipped.extend(vec![0u8; stride - width * cpp]);
|
||||
}
|
||||
|
||||
flipped
|
||||
}
|
||||
|
||||
/// Performs premultiplication of the pixels if
|
||||
/// UNPACK_PREMULTIPLY_ALPHA_WEBGL is currently enabled.
|
||||
fn premultiply_pixels(&self,
|
||||
format: TexFormat,
|
||||
data_type: TexDataType,
|
||||
pixels: Vec<u8>) -> Vec<u8> {
|
||||
if !self.texture_unpacking_settings.get().contains(PREMULTIPLY_ALPHA) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
match (format, data_type) {
|
||||
(TexFormat::RGBA, TexDataType::UnsignedByte) => {
|
||||
let mut premul = Vec::<u8>::with_capacity(pixels.len());
|
||||
for rgba in pixels.chunks(4) {
|
||||
premul.push(multiply_u8_pixel(rgba[0], rgba[3]));
|
||||
premul.push(multiply_u8_pixel(rgba[1], rgba[3]));
|
||||
premul.push(multiply_u8_pixel(rgba[2], rgba[3]));
|
||||
premul.push(rgba[3]);
|
||||
}
|
||||
premul
|
||||
}
|
||||
(TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
|
||||
let mut premul = Vec::<u8>::with_capacity(pixels.len());
|
||||
for la in pixels.chunks(2) {
|
||||
premul.push(multiply_u8_pixel(la[0], la[1]));
|
||||
premul.push(la[1]);
|
||||
}
|
||||
premul
|
||||
}
|
||||
|
||||
(TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
|
||||
let mut premul = Vec::<u8>::with_capacity(pixels.len());
|
||||
for mut rgba in pixels.chunks(2) {
|
||||
let pix = rgba.read_u16::<NativeEndian>().unwrap();
|
||||
if pix & (1 << 15) != 0 {
|
||||
premul.write_u16::<NativeEndian>(pix).unwrap();
|
||||
} else {
|
||||
premul.write_u16::<NativeEndian>(0).unwrap();
|
||||
}
|
||||
}
|
||||
premul
|
||||
}
|
||||
|
||||
(TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
|
||||
let mut premul = Vec::<u8>::with_capacity(pixels.len());
|
||||
for mut rgba in pixels.chunks(2) {
|
||||
let pix = rgba.read_u16::<NativeEndian>().unwrap();
|
||||
let extend_to_8_bits = |val| { (val | val << 4) as u8 };
|
||||
let r = extend_to_8_bits(pix & 0x000f);
|
||||
let g = extend_to_8_bits((pix & 0x00f0) >> 4);
|
||||
let b = extend_to_8_bits((pix & 0x0f00) >> 8);
|
||||
let a = extend_to_8_bits((pix & 0xf000) >> 12);
|
||||
|
||||
premul.write_u16::<NativeEndian>((multiply_u8_pixel(r, a) & 0xf0) as u16 >> 4 |
|
||||
(multiply_u8_pixel(g, a) & 0xf0) as u16 |
|
||||
((multiply_u8_pixel(b, a) & 0xf0) as u16) << 4 |
|
||||
pix & 0xf000).unwrap();
|
||||
}
|
||||
premul
|
||||
}
|
||||
|
||||
// Other formats don't have alpha, so return their data untouched.
|
||||
_ => pixels
|
||||
}
|
||||
}
|
||||
|
||||
fn tex_image_2d(&self,
|
||||
|
@ -480,14 +649,13 @@ impl WebGLRenderingContext {
|
|||
width: u32,
|
||||
height: u32,
|
||||
_border: u32,
|
||||
unpacking_alignment: u32,
|
||||
pixels: Vec<u8>) { // NB: pixels should NOT be premultipied
|
||||
if internal_format == TexFormat::RGBA &&
|
||||
data_type == TexDataType::UnsignedByte &&
|
||||
self.texture_unpacking_settings.get().contains(PREMULTIPLY_ALPHA) {
|
||||
// TODO(emilio): premultiply here.
|
||||
}
|
||||
// FINISHME: Consider doing premultiply and flip in a single mutable Vec.
|
||||
let pixels = self.premultiply_pixels(internal_format, data_type, pixels);
|
||||
|
||||
// TODO(emilio): Flip Y axis if necessary here
|
||||
let pixels = self.flip_teximage_y(pixels, internal_format, data_type,
|
||||
width as usize, height as usize, unpacking_alignment as usize);
|
||||
|
||||
// TexImage2D depth is always equal to 1
|
||||
handle_potential_webgl_error!(self, texture.initialize(target,
|
||||
|
@ -497,7 +665,16 @@ impl WebGLRenderingContext {
|
|||
level,
|
||||
Some(data_type)));
|
||||
|
||||
// TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested
|
||||
// Set the unpack alignment. For textures coming from arrays,
|
||||
// this will be the current value of the context's
|
||||
// GL_UNPACK_ALIGNMENT, while for textures from images or
|
||||
// canvas (produced by rgba8_image_to_tex_image_data()), it
|
||||
// will be 1.
|
||||
self.ipc_renderer
|
||||
.send(CanvasMsg::WebGL(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32)))
|
||||
.unwrap();
|
||||
|
||||
// TODO(emilio): convert colorspace if requested
|
||||
let msg = WebGLCommand::TexImage2D(target.as_gl_constant(), level as i32,
|
||||
internal_format.as_gl_constant() as i32,
|
||||
width as i32, height as i32,
|
||||
|
@ -523,6 +700,7 @@ impl WebGLRenderingContext {
|
|||
height: u32,
|
||||
format: TexFormat,
|
||||
data_type: TexDataType,
|
||||
unpacking_alignment: u32,
|
||||
pixels: Vec<u8>) { // NB: pixels should NOT be premultipied
|
||||
// We have already validated level
|
||||
let image_info = texture.image_info_for_target(&target, level);
|
||||
|
@ -542,9 +720,22 @@ impl WebGLRenderingContext {
|
|||
return self.webgl_error(InvalidOperation);
|
||||
}
|
||||
|
||||
// TODO(emilio): Flip Y axis if necessary here
|
||||
// FINISHME: Consider doing premultiply and flip in a single mutable Vec.
|
||||
let pixels = self.premultiply_pixels(format, data_type, pixels);
|
||||
|
||||
// TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested
|
||||
let pixels = self.flip_teximage_y(pixels, format, data_type,
|
||||
width as usize, height as usize, unpacking_alignment as usize);
|
||||
|
||||
// Set the unpack alignment. For textures coming from arrays,
|
||||
// this will be the current value of the context's
|
||||
// GL_UNPACK_ALIGNMENT, while for textures from images or
|
||||
// canvas (produced by rgba8_image_to_tex_image_data()), it
|
||||
// will be 1.
|
||||
self.ipc_renderer
|
||||
.send(CanvasMsg::WebGL(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32)))
|
||||
.unwrap();
|
||||
|
||||
// TODO(emilio): convert colorspace if requested
|
||||
let msg = WebGLCommand::TexSubImage2D(target.as_gl_constant(),
|
||||
level as i32, xoffset, yoffset,
|
||||
width as i32, height as i32,
|
||||
|
@ -1859,13 +2050,11 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
1 | 2 | 4 | 8 => (),
|
||||
_ => return self.webgl_error(InvalidValue),
|
||||
}
|
||||
self.texture_unpacking_alignment.set(param_value as u32);
|
||||
return;
|
||||
},
|
||||
_ => return self.webgl_error(InvalidEnum),
|
||||
}
|
||||
|
||||
self.ipc_renderer
|
||||
.send(CanvasMsg::WebGL(WebGLCommand::PixelStorei(param_name, param_value)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
|
||||
|
@ -2594,10 +2783,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
|
||||
};
|
||||
|
||||
let unpacking_alignment = self.texture_unpacking_alignment.get();
|
||||
|
||||
let expected_byte_length =
|
||||
match { self.validate_tex_image_2d_data(width, height,
|
||||
format, data_type,
|
||||
data_ptr) } {
|
||||
format, data_type,
|
||||
unpacking_alignment, data_ptr) } {
|
||||
Ok(byte_length) => byte_length,
|
||||
Err(()) => return Ok(()),
|
||||
};
|
||||
|
@ -2620,7 +2811,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
}
|
||||
|
||||
self.tex_image_2d(texture, target, data_type, format,
|
||||
level, width, height, border, buff);
|
||||
level, width, height, border, unpacking_alignment, buff);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2658,8 +2849,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
|
||||
};
|
||||
|
||||
let pixels = self.rgba8_image_to_tex_image_data(format, data_type, pixels);
|
||||
|
||||
self.tex_image_2d(texture, target, data_type, format,
|
||||
level, width, height, border, pixels);
|
||||
level, width, height, border, 1, pixels);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -2700,10 +2893,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
|
||||
};
|
||||
|
||||
let unpacking_alignment = self.texture_unpacking_alignment.get();
|
||||
|
||||
let expected_byte_length =
|
||||
match { self.validate_tex_image_2d_data(width, height,
|
||||
format, data_type,
|
||||
data_ptr) } {
|
||||
format, data_type,
|
||||
unpacking_alignment, data_ptr) } {
|
||||
Ok(byte_length) => byte_length,
|
||||
Err(()) => return Ok(()),
|
||||
};
|
||||
|
@ -2726,7 +2921,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
}
|
||||
|
||||
self.tex_sub_image_2d(texture, target, level, xoffset, yoffset,
|
||||
width, height, format, data_type, buff);
|
||||
width, height, format, data_type, unpacking_alignment, buff);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -2762,8 +2957,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
|
||||
};
|
||||
|
||||
let pixels = self.rgba8_image_to_tex_image_data(format, data_type, pixels);
|
||||
|
||||
self.tex_sub_image_2d(texture, target, level, xoffset, yoffset,
|
||||
width, height, format, data_type, pixels);
|
||||
width, height, format, data_type, 1, pixels);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ extern crate audio_video_metadata;
|
|||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate bluetooth_traits;
|
||||
extern crate byteorder;
|
||||
extern crate canvas_traits;
|
||||
extern crate caseless;
|
||||
extern crate cookie as cookie_rs;
|
||||
|
|
Загрузка…
Ссылка в новой задаче