Implement WebGL extensions.

This commit is contained in:
Imanol Fernandez 2017-05-16 11:14:23 +02:00
parent ac99a48aea
commit 32e23c4db4
40 changed files with 1346 additions and 86 deletions

View file

@ -4,7 +4,10 @@
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
use canvas_traits::{CanvasCommonMsg, CanvasMsg, byte_swap, multiply_u8_pixel};
use core::cell::Ref;
use core::iter::FromIterator;
use core::nonzero::NonZero;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes};
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
@ -20,6 +23,7 @@ use dom::globalscope::GlobalScope;
use dom::htmlcanvaselement::HTMLCanvasElement;
use dom::htmlcanvaselement::utils as canvas_utils;
use dom::node::{Node, NodeDamage, window_from_node};
use dom::webgl_extensions::WebGLExtensions;
use dom::webgl_validations::WebGLValidator;
use dom::webgl_validations::tex_image_2d::{CommonTexImage2DValidator, CommonTexImage2DValidatorResult};
use dom::webgl_validations::tex_image_2d::{TexImage2DValidator, TexImage2DValidatorResult};
@ -37,6 +41,7 @@ use dom::webgluniformlocation::WebGLUniformLocation;
use dom::window::Window;
use dom_struct::dom_struct;
use euclid::size::Size2D;
use half::f16;
use ipc_channel::ipc::{self, IpcSender};
use js::conversions::ConversionBehavior;
use js::jsapi::{JSContext, JSObject, Type, Rooted};
@ -47,6 +52,7 @@ use net_traits::image_cache::ImageResponse;
use offscreen_gl_context::{GLContextAttributes, GLLimits};
use script_traits::ScriptMsg as ConstellationMsg;
use std::cell::Cell;
use std::collections::HashMap;
use webrender_traits;
use webrender_traits::{WebGLCommand, WebGLError, WebGLFramebufferBindingRequest, WebGLParameter};
use webrender_traits::WebGLError::*;
@ -143,6 +149,7 @@ pub struct WebGLRenderingContext {
bound_texture_cube_map: MutNullableJS<WebGLTexture>,
bound_buffer_array: MutNullableJS<WebGLBuffer>,
bound_buffer_element_array: MutNullableJS<WebGLBuffer>,
bound_attrib_buffers: DOMRefCell<HashMap<u32, JS<WebGLBuffer>>>,
current_program: MutNullableJS<WebGLProgram>,
#[ignore_heap_size_of = "Because it's small"]
current_vertex_attrib_0: Cell<(f32, f32, f32, f32)>,
@ -150,6 +157,7 @@ pub struct WebGLRenderingContext {
current_scissor: Cell<(i32, i32, i32, i32)>,
#[ignore_heap_size_of = "Because it's small"]
current_clear_color: Cell<(f32, f32, f32, f32)>,
extension_manager: WebGLExtensions
}
impl WebGLRenderingContext {
@ -178,11 +186,13 @@ impl WebGLRenderingContext {
bound_texture_cube_map: MutNullableJS::new(None),
bound_buffer_array: MutNullableJS::new(None),
bound_buffer_element_array: MutNullableJS::new(None),
bound_attrib_buffers: DOMRefCell::new(HashMap::new()),
bound_renderbuffer: MutNullableJS::new(None),
current_program: MutNullableJS::new(None),
current_vertex_attrib_0: Cell::new((0f32, 0f32, 0f32, 1f32)),
current_scissor: Cell::new((0, 0, size.width, size.height)),
current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0))
current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)),
extension_manager: WebGLExtensions::new()
}
})
}
@ -221,6 +231,22 @@ impl WebGLRenderingContext {
}
}
pub fn borrow_bound_attrib_buffers(&self) -> Ref<HashMap<u32, JS<WebGLBuffer>>> {
self.bound_attrib_buffers.borrow()
}
pub fn set_bound_attrib_buffers<'a, T>(&self, iter: T) where T: Iterator<Item=(u32, &'a WebGLBuffer)> {
*self.bound_attrib_buffers.borrow_mut() = HashMap::from_iter(iter.map(|(k,v)| (k, JS::from_ref(v))));
}
pub fn bound_buffer_element_array(&self) -> Option<Root<WebGLBuffer>> {
self.bound_buffer_element_array.get()
}
pub fn set_bound_buffer_element_array(&self, buffer: Option<&WebGLBuffer>) {
self.bound_buffer_element_array.set(buffer);
}
pub fn recreate(&self, size: Size2D<i32>) {
self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size))).unwrap();
@ -245,6 +271,14 @@ impl WebGLRenderingContext {
self.ipc_renderer.clone()
}
pub fn send_renderer_message(&self, msg: CanvasMsg) {
self.ipc_renderer.send(msg).unwrap();
}
pub fn get_extension_manager<'a>(&'a self) -> &'a WebGLExtensions {
&self.extension_manager
}
pub fn webgl_error(&self, err: WebGLError) {
// TODO(emilio): Add useful debug messages to this
warn!("WebGL error: {:?}, previous error was {:?}", err, self.last_error.get());
@ -296,6 +330,23 @@ impl WebGLRenderingContext {
};
if let Some(texture) = texture {
handle_potential_webgl_error!(self, texture.tex_parameter(target, name, value));
// Validate non filterable TEXTURE_2D data_types
if target != constants::TEXTURE_2D {
return;
}
let target = TexImageTarget::Texture2D;
let info = texture.image_info_for_target(&target, 0);
if info.is_initialized() {
self.validate_filterable_texture(&texture,
target,
0,
info.internal_format().unwrap_or(TexFormat::RGBA),
info.width(),
info.height(),
info.data_type().unwrap_or(TexDataType::UnsignedByte));
}
} else {
self.webgl_error(InvalidOperation)
}
@ -329,6 +380,37 @@ impl WebGLRenderingContext {
}
}
// LINEAR filtering may be forbidden when using WebGL extensions.
// https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/
fn validate_filterable_texture(&self,
texture: &WebGLTexture,
target: TexImageTarget,
level: u32,
format: TexFormat,
width: u32,
height: u32,
data_type: TexDataType)
-> bool
{
if self.extension_manager.is_filterable(data_type.as_gl_constant()) || !texture.is_using_linear_filtering() {
return true;
}
// Handle validation failed: LINEAR filtering not valid for this texture
// WebGL Conformance tests expect to fallback to [0, 0, 0, 255] RGBA UNSIGNED_BYTE
let data_type = TexDataType::UnsignedByte;
let expected_byte_length = width * height * 4;
let mut pixels = vec![0u8; expected_byte_length as usize];
for rgba8 in pixels.chunks_mut(4) {
rgba8[3] = 255u8;
}
let pixels = self.prepare_pixels(format, data_type, width, height, 1, true, true, pixels);
self.tex_image_2d(texture, target, data_type, format, level, width, height, 0, 1, pixels);
false
}
fn validate_stencil_actions(&self, action: u32) -> bool {
match action {
0 | constants::KEEP | constants::REPLACE | constants::INCR | constants::DECR |
@ -369,6 +451,14 @@ impl WebGLRenderingContext {
true
}
// 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.
///
@ -391,7 +481,6 @@ impl WebGLRenderingContext {
(TexFormat::RGBA, TexDataType::UnsignedByte) => pixels,
(TexFormat::RGB, TexDataType::UnsignedByte) => {
// Remove alpha channel
let pixel_count = pixels.len() / 4;
let mut rgb8 = Vec::<u8>::with_capacity(pixel_count * 3);
for rgba8 in pixels.chunks(4) {
rgb8.push(rgba8[0]);
@ -400,6 +489,32 @@ impl WebGLRenderingContext {
}
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) {
@ -432,10 +547,107 @@ impl WebGLRenderingContext {
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!()
_ => unreachable!("Unsupported formats {:?} {:?}", format, data_type)
}
}
@ -522,9 +734,11 @@ impl WebGLRenderingContext {
// If it is UNSIGNED_BYTE, a Uint8Array must be supplied;
// if it is UNSIGNED_SHORT_5_6_5, UNSIGNED_SHORT_4_4_4_4,
// or UNSIGNED_SHORT_5_5_5_1, a Uint16Array must be supplied.
// or FLOAT, a Float32Array must be supplied.
// If the types do not match, an INVALID_OPERATION error is generated.
typedarray!(in(cx) let typedarray_u8: Uint8Array = data);
typedarray!(in(cx) let typedarray_u16: Uint16Array = data);
typedarray!(in(cx) let typedarray_f32: Float32Array = data);
let received_size = if data.is_null() {
element_size
} else {
@ -532,6 +746,8 @@ impl WebGLRenderingContext {
2
} else if typedarray_u8.is_ok() {
1
} else if typedarray_f32.is_ok() {
4
} else {
self.webgl_error(InvalidOperation);
return Err(());
@ -700,7 +916,7 @@ impl WebGLRenderingContext {
}
fn tex_image_2d(&self,
texture: Root<WebGLTexture>,
texture: &WebGLTexture,
target: TexImageTarget,
data_type: TexDataType,
internal_format: TexFormat,
@ -728,12 +944,17 @@ impl WebGLRenderingContext {
.send(CanvasMsg::WebGL(WebGLCommand::PixelStorei(constants::UNPACK_ALIGNMENT, unpacking_alignment as i32)))
.unwrap();
let format = internal_format.as_gl_constant();
let data_type = data_type.as_gl_constant();
let internal_format = self.extension_manager.get_effective_tex_internal_format(format, data_type);
// TODO(emilio): convert colorspace if requested
let msg = WebGLCommand::TexImage2D(target.as_gl_constant(), level as i32,
internal_format.as_gl_constant() as i32,
internal_format as i32,
width as i32, height as i32,
internal_format.as_gl_constant(),
data_type.as_gl_constant(), pixels);
format,
data_type,
pixels);
self.ipc_renderer
.send(CanvasMsg::WebGL(msg))
@ -807,6 +1028,14 @@ impl WebGLRenderingContext {
},
}
}
fn get_gl_extensions(&self) -> String {
let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap();
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::GetExtensions(sender)))
.unwrap();
receiver.recv().unwrap()
}
}
impl Drop for WebGLRenderingContext {
@ -958,6 +1187,19 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
_ => {}
}
// Handle GetParameter getters injected via WebGL extensions
if let Some(query_handler) = self.extension_manager.get_query_parameter_handler(parameter) {
match query_handler(cx, &self) {
Ok(value) => {
return value;
},
Err(error) => {
self.webgl_error(error);
return NullValue();
}
}
}
let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap();
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::GetParameter(parameter, sender)))
@ -1019,14 +1261,21 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14
fn GetSupportedExtensions(&self) -> Option<Vec<DOMString>> {
Some(vec![])
self.extension_manager.init_once(|| {
self.get_gl_extensions()
});
let extensions = self.extension_manager.get_suported_extensions();
Some(extensions.iter().map(|name| DOMString::from(*name)).collect())
}
#[allow(unsafe_code)]
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14
unsafe fn GetExtension(&self, _cx: *mut JSContext, _name: DOMString)
unsafe fn GetExtension(&self, _cx: *mut JSContext, name: DOMString)
-> Option<NonZero<*mut JSObject>> {
None
self.extension_manager.init_once(|| {
self.get_gl_extensions()
});
self.extension_manager.get_or_init_extension(&name, self)
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
@ -1627,6 +1876,23 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
fn DeleteBuffer(&self, buffer: Option<&WebGLBuffer>) {
if let Some(buffer) = buffer {
if buffer.is_attached_to_vao() {
// WebGL spec: The buffers attached to VAOs should still not be deleted.
// They are deleted after the VAO is deleted.
buffer.set_pending_delete();
return;
}
// Remove deleted buffer from bound attrib buffers.
let attrib_ids: Vec<_> = self.bound_attrib_buffers.borrow().iter()
.filter(|&(_, v)| v.id() == buffer.id())
.map(|(&k, _)| k)
.collect();
for id in attrib_ids {
self.bound_attrib_buffers.borrow_mut().remove(&id);
}
// Delete buffer.
handle_object_deletion!(self, self.bound_buffer_array, buffer,
Some(WebGLCommand::BindBuffer(constants::ARRAY_BUFFER, None)));
handle_object_deletion!(self, self.bound_buffer_element_array, buffer,
@ -1989,6 +2255,14 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return result.get()
}
if pname == constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING {
rooted!(in(cx) let mut jsval = NullValue());
if let Some(buffer) = self.bound_attrib_buffers.borrow().get(&index) {
buffer.to_jsval(cx, jsval.handle_mut());
}
return jsval.get();
}
let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap();
self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::GetVertexAttrib(index, pname, sender))).unwrap();
@ -2006,6 +2280,14 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
}
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
fn GetVertexAttribOffset(&self, index: u32, pname: u32) -> i64 {
let (sender, receiver) = webrender_traits::channel::msg_channel().unwrap();
self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::GetVertexAttribOffset(index, pname, sender))).unwrap();
handle_potential_webgl_error!(self, receiver.recv().unwrap(), 0) as i64
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
fn Hint(&self, target: u32, mode: u32) {
if target != constants::GENERATE_MIPMAP_HINT {
@ -2784,9 +3066,13 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
if size < 1 || size > 4 {
return self.webgl_error(InvalidValue);
}
if self.bound_buffer_array.get().is_none() {
return self.webgl_error(InvalidOperation);
}
let buffer_array = match self.bound_buffer_array.get() {
Some(buffer) => buffer,
None => {
return self.webgl_error(InvalidOperation);
}
};
// stride and offset must be multiple of data_type
match data_type {
@ -2805,6 +3091,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
}
self.bound_attrib_buffers.borrow_mut().insert(attrib_id, JS::from_ref(&*buffer_array));
let msg = CanvasMsg::WebGL(
WebGLCommand::VertexAttribPointer(attrib_id, size, data_type, normalized, stride, offset as u32));
self.ipc_renderer.send(msg).unwrap()
@ -2834,6 +3122,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
format: u32,
data_type: u32,
data_ptr: *mut JSObject) -> Fallible<()> {
if !self.extension_manager.is_tex_type_enabled(data_type) {
return Ok(self.webgl_error(InvalidEnum));
}
let data = if data_ptr.is_null() {
None
} else {
@ -2885,10 +3177,14 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return Ok(self.webgl_error(InvalidOperation));
}
if !self.validate_filterable_texture(&texture, target, level, format, width, height, data_type) {
return Ok(()); // The validator sets the correct error for use
}
let pixels = self.prepare_pixels(format, data_type, width, height,
unpacking_alignment, false, false, buff);
self.tex_image_2d(texture, target, data_type, format,
self.tex_image_2d(&texture, target, data_type, format,
level, width, height, border, unpacking_alignment, pixels);
Ok(())
@ -2902,6 +3198,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
format: u32,
data_type: u32,
source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) -> Fallible<()> {
if !self.extension_manager.is_tex_type_enabled(data_type) {
return Ok(self.webgl_error(InvalidEnum));
}
// Get pixels from image source
let (pixels, size, premultiplied) = match self.get_image_pixels(source) {
Ok((pixels, size, premultiplied)) => (pixels, size, premultiplied),
@ -2927,11 +3227,15 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
Err(_) => return Ok(()), // NB: The validator sets the correct error for us.
};
if !self.validate_filterable_texture(&texture, target, level, format, width, height, data_type) {
return Ok(()); // The validator sets the correct error for use
}
let unpacking_alignment = 1;
let pixels = self.prepare_pixels(format, data_type, width, height,
unpacking_alignment, premultiplied, true, pixels);
self.tex_image_2d(texture, target, data_type, format,
self.tex_image_2d(&texture, target, data_type, format,
level, width, height, border, 1, pixels);
Ok(())
}
@ -2955,7 +3259,6 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
Some(try!(fallible_array_buffer_view_to_vec(cx, data_ptr)))
};
let validator = TexImage2DValidator::new(self, target, level,
format, width, height,
0, format, data_type);