Auto merge of #21641 - servo:webgl, r=jdm

Some drive-by perf improvements on the WebGL stack

This avoids a bunch of pointless buffer and texture copies, but is far from eliminating all which could be eliminated.

<!-- 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/21641)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-09-08 10:33:21 -04:00 committed by GitHub
commit 5929086c36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 301 additions and 298 deletions

View file

@ -7,8 +7,8 @@ use canvas_traits::webgl::*;
use euclid::Size2D; use euclid::Size2D;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use gleam::gl; use gleam::gl;
use ipc_channel::ipc::IpcBytesSender;
use offscreen_gl_context::{GLContext, GLContextAttributes, GLLimits, NativeGLContextMethods}; use offscreen_gl_context::{GLContext, GLContextAttributes, GLLimits, NativeGLContextMethods};
use serde_bytes::ByteBuf;
use std::thread; use std::thread;
use super::gl_context::{GLContextFactory, GLContextWrapper}; use super::gl_context::{GLContextFactory, GLContextWrapper};
use webrender; use webrender;
@ -713,8 +713,9 @@ impl WebGLImpl {
ctx.gl().pixel_store_i(name, val), ctx.gl().pixel_store_i(name, val),
WebGLCommand::PolygonOffset(factor, units) => WebGLCommand::PolygonOffset(factor, units) =>
ctx.gl().polygon_offset(factor, units), ctx.gl().polygon_offset(factor, units),
WebGLCommand::ReadPixels(x, y, width, height, format, pixel_type, ref chan) => WebGLCommand::ReadPixels(x, y, width, height, format, pixel_type, ref chan) => {
Self::read_pixels(ctx.gl(), x, y, width, height, format, pixel_type, chan), Self::read_pixels(ctx.gl(), x, y, width, height, format, pixel_type, chan)
}
WebGLCommand::RenderbufferStorage(target, format, width, height) => WebGLCommand::RenderbufferStorage(target, format, width, height) =>
ctx.gl().renderbuffer_storage(target, format, width, height), ctx.gl().renderbuffer_storage(target, format, width, height),
WebGLCommand::SampleCoverage(value, invert) => WebGLCommand::SampleCoverage(value, invert) =>
@ -833,11 +834,32 @@ impl WebGLImpl {
WebGLCommand::SetViewport(x, y, width, height) => { WebGLCommand::SetViewport(x, y, width, height) => {
ctx.gl().viewport(x, y, width, height); ctx.gl().viewport(x, y, width, height);
} }
WebGLCommand::TexImage2D(target, level, internal, width, height, format, data_type, ref data) => WebGLCommand::TexImage2D(target, level, internal, width, height, format, data_type, ref chan) => {
ctx.gl().tex_image_2d(target, level, internal, width, height, ctx.gl().tex_image_2d(
/*border*/0, format, data_type, Some(data)), target,
WebGLCommand::TexSubImage2D(target, level, xoffset, yoffset, x, y, width, height, ref data) => level,
ctx.gl().tex_sub_image_2d(target, level, xoffset, yoffset, x, y, width, height, data), internal,
width,
height,
0,
format,
data_type,
Some(&chan.recv().unwrap()),
)
}
WebGLCommand::TexSubImage2D(target, level, xoffset, yoffset, x, y, width, height, ref chan) => {
ctx.gl().tex_sub_image_2d(
target,
level,
xoffset,
yoffset,
x,
y,
width,
height,
&chan.recv().unwrap(),
)
}
WebGLCommand::DrawingBufferWidth(ref sender) => WebGLCommand::DrawingBufferWidth(ref sender) =>
sender.send(ctx.borrow_draw_buffer().unwrap().size().width).unwrap(), sender.send(ctx.borrow_draw_buffer().unwrap().size().width).unwrap(),
WebGLCommand::DrawingBufferHeight(ref sender) => WebGLCommand::DrawingBufferHeight(ref sender) =>
@ -1165,10 +1187,10 @@ impl WebGLImpl {
height: i32, height: i32,
format: u32, format: u32,
pixel_type: u32, pixel_type: u32,
chan: &WebGLSender<ByteBuf>, chan: &IpcBytesSender,
) { ) {
let result = gl.read_pixels(x, y, width, height, format, pixel_type); let result = gl.read_pixels(x, y, width, height, format, pixel_type);
chan.send(result.into()).unwrap() chan.send(&result).unwrap()
} }
fn finish(gl: &gl::Gl, chan: &WebGLSender<()>) { fn finish(gl: &gl::Gl, chan: &WebGLSender<()>) {

View file

@ -4,7 +4,7 @@
use euclid::Size2D; use euclid::Size2D;
use gleam::gl; use gleam::gl;
use ipc_channel::ipc::IpcBytesReceiver; use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender};
use offscreen_gl_context::{GLContextAttributes, GLLimits}; use offscreen_gl_context::{GLContextAttributes, GLLimits};
use serde_bytes::ByteBuf; use serde_bytes::ByteBuf;
use std::borrow::Cow; use std::borrow::Cow;
@ -214,7 +214,7 @@ pub enum WebGLCommand {
GetRenderbufferParameter(u32, u32, WebGLSender<i32>), GetRenderbufferParameter(u32, u32, WebGLSender<i32>),
PolygonOffset(f32, f32), PolygonOffset(f32, f32),
RenderbufferStorage(u32, u32, i32, i32), RenderbufferStorage(u32, u32, i32, i32),
ReadPixels(i32, i32, i32, i32, u32, u32, WebGLSender<ByteBuf>), ReadPixels(i32, i32, i32, i32, u32, u32, IpcBytesSender),
SampleCoverage(f32, bool), SampleCoverage(f32, bool),
Scissor(i32, i32, i32, i32), Scissor(i32, i32, i32, i32),
StencilFunc(u32, i32, u32), StencilFunc(u32, i32, u32),
@ -252,8 +252,8 @@ pub enum WebGLCommand {
VertexAttribPointer(u32, i32, u32, bool, i32, u32), VertexAttribPointer(u32, i32, u32, bool, i32, u32),
VertexAttribPointer2f(u32, i32, bool, i32, u32), VertexAttribPointer2f(u32, i32, bool, i32, u32),
SetViewport(i32, i32, i32, i32), SetViewport(i32, i32, i32, i32),
TexImage2D(u32, i32, i32, i32, i32, u32, u32, ByteBuf), TexImage2D(u32, i32, i32, i32, i32, u32, u32, IpcBytesReceiver),
TexSubImage2D(u32, i32, i32, i32, i32, i32, u32, u32, ByteBuf), TexSubImage2D(u32, i32, i32, i32, i32, i32, u32, u32, IpcBytesReceiver),
DrawingBufferWidth(WebGLSender<i32>), DrawingBufferWidth(WebGLSender<i32>),
DrawingBufferHeight(WebGLSender<i32>), DrawingBufferHeight(WebGLSender<i32>),
Finish(WebGLSender<()>), Finish(WebGLSender<()>),

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{ByteOrder, NativeEndian, WriteBytesExt};
use canvas_traits::canvas::{byte_swap, multiply_u8_pixel}; use canvas_traits::canvas::{byte_swap, multiply_u8_pixel};
use canvas_traits::webgl::{DOMToTextureCommand, Parameter}; use canvas_traits::webgl::{DOMToTextureCommand, Parameter};
use canvas_traits::webgl::{TexParameter, WebGLCommand, WebGLContextShareMode, WebGLError}; use canvas_traits::webgl::{TexParameter, WebGLCommand, WebGLContextShareMode, WebGLError};
@ -481,206 +481,6 @@ impl WebGLRenderingContext {
} }
} }
// https://en.wikipedia.org/wiki/Relative_luminance
#[inline]
fn luminance(r: u8, g: u8, b: u8) -> u8 {
(0.2126 * (r as f32) +
0.7152 * (g as f32) +
0.0722 * (b as f32)) as u8
}
/// 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) => {
// Remove alpha channel
let mut rgb8 = Vec::<u8>::with_capacity(pixel_count * 3);
for rgba8 in pixels.chunks(4) {
rgb8.push(rgba8[0]);
rgb8.push(rgba8[1]);
rgb8.push(rgba8[2]);
}
rgb8
},
(TexFormat::Alpha, TexDataType::UnsignedByte) => {
let mut alpha = Vec::<u8>::with_capacity(pixel_count);
for rgba8 in pixels.chunks(4) {
alpha.push(rgba8[3]);
}
alpha
},
(TexFormat::Luminance, TexDataType::UnsignedByte) => {
let mut luminance = Vec::<u8>::with_capacity(pixel_count);
for rgba8 in pixels.chunks(4) {
luminance.push(Self::luminance(rgba8[0], rgba8[1], rgba8[2]));
}
luminance
},
(TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 2);
for rgba8 in pixels.chunks(4) {
data.push(Self::luminance(rgba8[0], rgba8[1], rgba8[2]));
data.push(rgba8[3]);
}
data
},
(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
}
(TexFormat::RGBA, TexDataType::Float) => {
let mut rgbaf32 = Vec::<u8>::with_capacity(pixel_count * 16);
for rgba8 in pixels.chunks(4) {
rgbaf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
}
rgbaf32
}
(TexFormat::RGB, TexDataType::Float) => {
let mut rgbf32 = Vec::<u8>::with_capacity(pixel_count * 12);
for rgba8 in pixels.chunks(4) {
rgbf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
rgbf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
rgbf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
}
rgbf32
}
(TexFormat::Alpha, TexDataType::Float) => {
let mut alpha = Vec::<u8>::with_capacity(pixel_count * 4);
for rgba8 in pixels.chunks(4) {
alpha.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
}
alpha
},
(TexFormat::Luminance, TexDataType::Float) => {
let mut luminance = Vec::<u8>::with_capacity(pixel_count * 4);
for rgba8 in pixels.chunks(4) {
let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]);
luminance.write_f32::<NativeEndian>(p as f32).unwrap();
}
luminance
},
(TexFormat::LuminanceAlpha, TexDataType::Float) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]);
data.write_f32::<NativeEndian>(p as f32).unwrap();
data.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
}
data
},
(TexFormat::RGBA, TexDataType::HalfFloat) => {
let mut rgbaf16 = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap();
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap();
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap();
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap();
}
rgbaf16
},
(TexFormat::RGB, TexDataType::HalfFloat) => {
let mut rgbf16 = Vec::<u8>::with_capacity(pixel_count * 6);
for rgba8 in pixels.chunks(4) {
rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap();
rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap();
rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap();
}
rgbf16
},
(TexFormat::Alpha, TexDataType::HalfFloat) => {
let mut alpha = Vec::<u8>::with_capacity(pixel_count * 2);
for rgba8 in pixels.chunks(4) {
alpha.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap();
}
alpha
},
(TexFormat::Luminance, TexDataType::HalfFloat) => {
let mut luminance = Vec::<u8>::with_capacity(pixel_count * 4);
for rgba8 in pixels.chunks(4) {
let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]);
luminance.write_u16::<NativeEndian>(f16::from_f32(p as f32).as_bits()).unwrap();
}
luminance
},
(TexFormat::LuminanceAlpha, TexDataType::HalfFloat) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
let p = Self::luminance(rgba8[0], rgba8[1], rgba8[2]);
data.write_u16::<NativeEndian>(f16::from_f32(p as f32).as_bits()).unwrap();
data.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap();
}
data
},
// 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!("Unsupported formats {:?} {:?}", format, data_type)
}
}
fn get_image_pixels( fn get_image_pixels(
&self, &self,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
@ -825,85 +625,52 @@ impl WebGLRenderingContext {
/// Performs premultiplication of the pixels if /// Performs premultiplication of the pixels if
/// UNPACK_PREMULTIPLY_ALPHA_WEBGL is currently enabled. /// UNPACK_PREMULTIPLY_ALPHA_WEBGL is currently enabled.
fn premultiply_pixels(&self, fn premultiply_pixels(&self, format: TexFormat, data_type: TexDataType, pixels: &mut [u8]) {
format: TexFormat,
data_type: TexDataType,
pixels: Vec<u8>) -> Vec<u8> {
if !self.texture_unpacking_settings.get().contains(TextureUnpacking::PREMULTIPLY_ALPHA) { if !self.texture_unpacking_settings.get().contains(TextureUnpacking::PREMULTIPLY_ALPHA) {
return pixels; return;
} }
match (format, data_type) { match (format, data_type) {
(TexFormat::RGBA, TexDataType::UnsignedByte) => { (TexFormat::RGBA, TexDataType::UnsignedByte) => {
let mut premul = Vec::<u8>::with_capacity(pixels.len()); for rgba in pixels.chunks_mut(4) {
for rgba in pixels.chunks(4) { rgba[0] = multiply_u8_pixel(rgba[0], rgba[3]);
premul.push(multiply_u8_pixel(rgba[0], rgba[3])); rgba[1] = multiply_u8_pixel(rgba[1], rgba[3]);
premul.push(multiply_u8_pixel(rgba[1], rgba[3])); rgba[2] = multiply_u8_pixel(rgba[2], rgba[3]);
premul.push(multiply_u8_pixel(rgba[2], rgba[3]));
premul.push(rgba[3]);
} }
premul },
}
(TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => { (TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
let mut premul = Vec::<u8>::with_capacity(pixels.len()); for la in pixels.chunks_mut(2) {
for la in pixels.chunks(2) { la[0] = multiply_u8_pixel(la[0], la[1]);
premul.push(multiply_u8_pixel(la[0], la[1]));
premul.push(la[1]);
} }
premul },
}
(TexFormat::RGBA, TexDataType::UnsignedShort5551) => { (TexFormat::RGBA, TexDataType::UnsignedShort5551) => {
let mut premul = Vec::<u8>::with_capacity(pixels.len()); for rgba in pixels.chunks_mut(2) {
for mut rgba in pixels.chunks(2) { let pix = NativeEndian::read_u16(rgba);
let pix = rgba.read_u16::<NativeEndian>().unwrap(); NativeEndian::write_u16(rgba, if pix & (1 << 15) != 0 { pix } else { 0 });
if pix & (1 << 15) != 0 {
premul.write_u16::<NativeEndian>(pix).unwrap();
} else {
premul.write_u16::<NativeEndian>(0).unwrap();
}
} }
premul },
}
(TexFormat::RGBA, TexDataType::UnsignedShort4444) => { (TexFormat::RGBA, TexDataType::UnsignedShort4444) => {
let mut premul = Vec::<u8>::with_capacity(pixels.len()); for rgba in pixels.chunks_mut(2) {
for mut rgba in pixels.chunks(2) { let pix = NativeEndian::read_u16(rgba);
let pix = rgba.read_u16::<NativeEndian>().unwrap();
let extend_to_8_bits = |val| { (val | val << 4) as u8 }; let extend_to_8_bits = |val| { (val | val << 4) as u8 };
let r = extend_to_8_bits(pix & 0x000f); let r = extend_to_8_bits(pix & 0x000f);
let g = extend_to_8_bits((pix & 0x00f0) >> 4); let g = extend_to_8_bits((pix & 0x00f0) >> 4);
let b = extend_to_8_bits((pix & 0x0f00) >> 8); let b = extend_to_8_bits((pix & 0x0f00) >> 8);
let a = extend_to_8_bits((pix & 0xf000) >> 12); let a = extend_to_8_bits((pix & 0xf000) >> 12);
NativeEndian::write_u16(
premul.write_u16::<NativeEndian>((multiply_u8_pixel(r, a) & 0xf0) as u16 >> 4 | rgba,
(multiply_u8_pixel(g, a) & 0xf0) as u16 | (multiply_u8_pixel(r, a) & 0xf0) as u16 >> 4 |
((multiply_u8_pixel(b, a) & 0xf0) as u16) << 4 | (multiply_u8_pixel(g, a) & 0xf0) as u16 |
pix & 0xf000).unwrap(); ((multiply_u8_pixel(b, a) & 0xf0) as u16) << 4 |
pix & 0xf000,
);
} }
premul },
}
// Other formats don't have alpha, so return their data untouched. // Other formats don't have alpha, so return their data untouched.
_ => pixels _ => {},
} }
} }
// Remove premultiplied alpha.
// This is only called when texImage2D is called using a canvas2d source and
// UNPACK_PREMULTIPLY_ALPHA_WEBGL is disabled. Pixels got from a canvas2D source
// are always RGBA8 with premultiplied alpha, so we don't have to worry about
// additional formats as happens in the premultiply_pixels method.
fn remove_premultiplied_alpha(&self, mut pixels: Vec<u8>) -> Vec<u8> {
for rgba in pixels.chunks_mut(4) {
let a = (rgba[3] as f32) / 255.0;
rgba[0] = (rgba[0] as f32 / a) as u8;
rgba[1] = (rgba[1] as f32 / a) as u8;
rgba[2] = (rgba[2] as f32 / a) as u8;
}
pixels
}
fn prepare_pixels(&self, fn prepare_pixels(&self,
internal_format: TexFormat, internal_format: TexFormat,
data_type: TexDataType, data_type: TexDataType,
@ -917,16 +684,16 @@ impl WebGLRenderingContext {
if !source_premultiplied && dest_premultiply { if !source_premultiplied && dest_premultiply {
if source_from_image_or_canvas { if source_from_image_or_canvas {
// When the pixels come from image or canvas or imagedata, use RGBA8 format // When the pixels come from image or canvas or imagedata, use RGBA8 format
pixels = self.premultiply_pixels(TexFormat::RGBA, TexDataType::UnsignedByte, pixels); self.premultiply_pixels(TexFormat::RGBA, TexDataType::UnsignedByte, &mut pixels);
} else { } else {
pixels = self.premultiply_pixels(internal_format, data_type, pixels); self.premultiply_pixels(internal_format, data_type, &mut pixels);
} }
} else if source_premultiplied && !dest_premultiply { } else if source_premultiplied && !dest_premultiply {
pixels = self.remove_premultiplied_alpha(pixels); remove_premultiplied_alpha(&mut pixels);
} }
if source_from_image_or_canvas { if source_from_image_or_canvas {
pixels = self.rgba8_image_to_tex_image_data(internal_format, data_type, pixels); pixels = rgba8_image_to_tex_image_data(internal_format, data_type, pixels);
} }
// FINISHME: Consider doing premultiply and flip in a single mutable Vec. // FINISHME: Consider doing premultiply and flip in a single mutable Vec.
@ -966,17 +733,17 @@ impl WebGLRenderingContext {
let internal_format = self.extension_manager.get_effective_tex_internal_format(format, data_type); let internal_format = self.extension_manager.get_effective_tex_internal_format(format, data_type);
// TODO(emilio): convert colorspace if requested // TODO(emilio): convert colorspace if requested
let msg = WebGLCommand::TexImage2D( let (sender, receiver) = ipc::bytes_channel().unwrap();
self.send_command(WebGLCommand::TexImage2D(
target.as_gl_constant(), target.as_gl_constant(),
level as i32, level as i32,
internal_format as i32, internal_format as i32,
width as i32, height as i32, width as i32, height as i32,
format, format,
data_type, data_type,
pixels.into(), receiver,
); ));
sender.send(&pixels).unwrap();
self.send_command(msg);
if let Some(fb) = self.bound_framebuffer.get() { if let Some(fb) = self.bound_framebuffer.get() {
fb.invalidate_texture(&*texture); fb.invalidate_texture(&*texture);
@ -1021,7 +788,8 @@ impl WebGLRenderingContext {
self.send_command(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32)); self.send_command(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32));
// TODO(emilio): convert colorspace if requested // TODO(emilio): convert colorspace if requested
let msg = WebGLCommand::TexSubImage2D( let (sender, receiver) = ipc::bytes_channel().unwrap();
self.send_command(WebGLCommand::TexSubImage2D(
target.as_gl_constant(), target.as_gl_constant(),
level as i32, level as i32,
xoffset, xoffset,
@ -1030,10 +798,9 @@ impl WebGLRenderingContext {
height as i32, height as i32,
format.as_gl_constant(), format.as_gl_constant(),
data_type.as_gl_constant(), data_type.as_gl_constant(),
pixels.into(), receiver,
); ));
sender.send(&pixels).unwrap();
self.send_command(msg);
} }
fn get_gl_extensions(&self) -> String { fn get_gl_extensions(&self) -> String {
@ -1191,18 +958,18 @@ impl WebGLRenderingContext {
// can fail and that it is UB what happens in that case. // can fail and that it is UB what happens in that case.
// //
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2 // https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2
pub fn get_image_data(&self, mut width: u32, mut height: u32) -> Option<Vec<u8>> { pub fn get_image_data(&self, width: u32, height: u32) -> Option<Vec<u8>> {
handle_potential_webgl_error!(self, self.validate_framebuffer(), return None); handle_potential_webgl_error!(self, self.validate_framebuffer(), return None);
if let Some((fb_width, fb_height)) = self.get_current_framebuffer_size() { let (fb_width, fb_height) = handle_potential_webgl_error!(
width = cmp::min(width, fb_width as u32); self,
height = cmp::min(height, fb_height as u32); self.get_current_framebuffer_size().ok_or(InvalidOperation),
} else { return None
self.webgl_error(InvalidOperation); );
return None; let width = cmp::min(width, fb_width as u32);
} let height = cmp::min(height, fb_height as u32);
let (sender, receiver) = webgl_channel().unwrap(); let (sender, receiver) = ipc::bytes_channel().unwrap();
self.send_command(WebGLCommand::ReadPixels( self.send_command(WebGLCommand::ReadPixels(
0, 0,
0, 0,
@ -1212,7 +979,7 @@ impl WebGLRenderingContext {
constants::UNSIGNED_BYTE, constants::UNSIGNED_BYTE,
sender, sender,
)); ));
Some(receiver.recv().unwrap().into()) Some(receiver.recv().unwrap())
} }
pub fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> { pub fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> {
@ -2894,7 +2661,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
_ => return self.webgl_error(InvalidOperation), _ => return self.webgl_error(InvalidOperation),
}; };
let (sender, receiver) = webgl_channel().unwrap(); let (sender, receiver) = ipc::bytes_channel().unwrap();
self.send_command(WebGLCommand::ReadPixels(x, y, width, height, format, pixel_type, sender)); self.send_command(WebGLCommand::ReadPixels(x, y, width, height, format, pixel_type, sender));
let result = receiver.recv().unwrap(); let result = receiver.recv().unwrap();
@ -4212,3 +3979,217 @@ impl TextureUnit {
None None
} }
} }
// Remove premultiplied alpha.
// This is only called when texImage2D is called using a canvas2d source and
// UNPACK_PREMULTIPLY_ALPHA_WEBGL is disabled. Pixels got from a canvas2D source
// are always RGBA8 with premultiplied alpha, so we don't have to worry about
// additional formats as happens in the premultiply_pixels method.
fn remove_premultiplied_alpha(pixels: &mut [u8]) {
for rgba in pixels.chunks_mut(4) {
let a = (rgba[3] as f32) / 255.0;
rgba[0] = (rgba[0] as f32 / a) as u8;
rgba[1] = (rgba[1] as f32 / a) as u8;
rgba[2] = (rgba[2] as f32 / a) as u8;
}
}
/// 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(
format: TexFormat,
data_type: TexDataType,
mut 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) => {
// Remove alpha channel
let mut rgb8 = Vec::<u8>::with_capacity(pixel_count * 3);
for rgba8 in pixels.chunks(4) {
rgb8.push(rgba8[0]);
rgb8.push(rgba8[1]);
rgb8.push(rgba8[2]);
}
rgb8
},
(TexFormat::Alpha, TexDataType::UnsignedByte) => {
let mut alpha = Vec::<u8>::with_capacity(pixel_count);
for rgba8 in pixels.chunks(4) {
alpha.push(rgba8[3]);
}
alpha
},
(TexFormat::Luminance, TexDataType::UnsignedByte) => {
let mut lum = Vec::<u8>::with_capacity(pixel_count);
for rgba8 in pixels.chunks(4) {
lum.push(luminance(rgba8[0], rgba8[1], rgba8[2]));
}
lum
},
(TexFormat::LuminanceAlpha, TexDataType::UnsignedByte) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 2);
for rgba8 in pixels.chunks(4) {
data.push(luminance(rgba8[0], rgba8[1], rgba8[2]));
data.push(rgba8[3]);
}
data
},
(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
}
(TexFormat::RGBA, TexDataType::Float) => {
let mut rgbaf32 = Vec::<u8>::with_capacity(pixel_count * 16);
for rgba8 in pixels.chunks(4) {
rgbaf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
rgbaf32.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
}
rgbaf32
}
(TexFormat::RGB, TexDataType::Float) => {
let mut rgbf32 = Vec::<u8>::with_capacity(pixel_count * 12);
for rgba8 in pixels.chunks(4) {
rgbf32.write_f32::<NativeEndian>(rgba8[0] as f32).unwrap();
rgbf32.write_f32::<NativeEndian>(rgba8[1] as f32).unwrap();
rgbf32.write_f32::<NativeEndian>(rgba8[2] as f32).unwrap();
}
rgbf32
}
(TexFormat::Alpha, TexDataType::Float) => {
for rgba8 in pixels.chunks_mut(4) {
let p = rgba8[0] as f32;
NativeEndian::write_f32(rgba8, p);
}
pixels
},
(TexFormat::Luminance, TexDataType::Float) => {
for rgba8 in pixels.chunks_mut(4) {
let p = luminance(rgba8[0], rgba8[1], rgba8[2]);
NativeEndian::write_f32(rgba8, p as f32);
}
pixels
},
(TexFormat::LuminanceAlpha, TexDataType::Float) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
let p = luminance(rgba8[0], rgba8[1], rgba8[2]);
data.write_f32::<NativeEndian>(p as f32).unwrap();
data.write_f32::<NativeEndian>(rgba8[3] as f32).unwrap();
}
data
},
(TexFormat::RGBA, TexDataType::HalfFloat) => {
let mut rgbaf16 = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap();
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap();
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap();
rgbaf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap();
}
rgbaf16
},
(TexFormat::RGB, TexDataType::HalfFloat) => {
let mut rgbf16 = Vec::<u8>::with_capacity(pixel_count * 6);
for rgba8 in pixels.chunks(4) {
rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[0] as f32).as_bits()).unwrap();
rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[1] as f32).as_bits()).unwrap();
rgbf16.write_u16::<NativeEndian>(f16::from_f32(rgba8[2] as f32).as_bits()).unwrap();
}
rgbf16
},
(TexFormat::Alpha, TexDataType::HalfFloat) => {
let mut alpha = Vec::<u8>::with_capacity(pixel_count * 2);
for rgba8 in pixels.chunks(4) {
alpha.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap();
}
alpha
},
(TexFormat::Luminance, TexDataType::HalfFloat) => {
let mut lum = Vec::<u8>::with_capacity(pixel_count * 2);
for rgba8 in pixels.chunks(4) {
let p = luminance(rgba8[0], rgba8[1], rgba8[2]);
lum.write_u16::<NativeEndian>(f16::from_f32(p as f32).as_bits()).unwrap();
}
lum
},
(TexFormat::LuminanceAlpha, TexDataType::HalfFloat) => {
let mut data = Vec::<u8>::with_capacity(pixel_count * 8);
for rgba8 in pixels.chunks(4) {
let p = luminance(rgba8[0], rgba8[1], rgba8[2]);
data.write_u16::<NativeEndian>(f16::from_f32(p as f32).as_bits()).unwrap();
data.write_u16::<NativeEndian>(f16::from_f32(rgba8[3] as f32).as_bits()).unwrap();
}
data
},
// 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!("Unsupported formats {:?} {:?}", format, data_type)
}
}
// https://en.wikipedia.org/wiki/Relative_luminance
#[inline]
fn luminance(r: u8, g: u8, b: u8) -> u8 {
(0.2126 * (r as f32) +
0.7152 * (g as f32) +
0.0722 * (b as f32)) as u8
}