Auto merge of #10443 - emilio:webgl-teximage2d-overload, r=jdm

webgl: Implement the pending texImage2D overload, and add more validation

This is a large-ish refactor of the Texture2D code, but it should be
easier to read and of course more correct.

I tried to annotate every error condition with a spec paragraph.

I made just a reftest to ensure this works as intended, since I expect #10373 to land pretty soon.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10443)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-04-22 11:25:29 -07:00
commit f1defb446e
15 changed files with 615 additions and 137 deletions

View file

@ -3,12 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use canvas_traits::{CanvasCommonMsg, CanvasMsg};
use dom::bindings::codegen::Bindings::WebGLActiveInfoBinding::WebGLActiveInfoMethods;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{WebGLRenderingContextMethods};
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes};
use dom::bindings::codegen::UnionTypes::ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement;
use dom::bindings::conversions::{ToJSValConvertible, array_buffer_view_to_vec_checked, array_buffer_view_to_vec};
use dom::bindings::conversions::{ToJSValConvertible, array_buffer_view_data_checked};
use dom::bindings::conversions::{array_buffer_view_to_vec_checked, array_buffer_view_to_vec};
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, Root};
@ -67,45 +67,6 @@ bitflags! {
}
}
pub enum UniformType {
Int,
IntVec2,
IntVec3,
IntVec4,
Float,
FloatVec2,
FloatVec3,
FloatVec4,
}
impl UniformType {
fn element_count(&self) -> usize {
match *self {
UniformType::Int => 1,
UniformType::IntVec2 => 2,
UniformType::IntVec3 => 3,
UniformType::IntVec4 => 4,
UniformType::Float => 1,
UniformType::FloatVec2 => 2,
UniformType::FloatVec3 => 3,
UniformType::FloatVec4 => 4,
}
}
fn as_gl_constant(&self) -> u32 {
match *self {
UniformType::Int => constants::INT,
UniformType::IntVec2 => constants::INT_VEC2,
UniformType::IntVec3 => constants::INT_VEC3,
UniformType::IntVec4 => constants::INT_VEC4,
UniformType::Float => constants::FLOAT,
UniformType::FloatVec2 => constants::FLOAT_VEC2,
UniformType::FloatVec3 => constants::FLOAT_VEC3,
UniformType::FloatVec4 => constants::FLOAT_VEC4,
}
}
}
#[dom_struct]
pub struct WebGLRenderingContext {
reflector_: Reflector,
@ -224,7 +185,7 @@ impl WebGLRenderingContext {
// https://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf#nameddest=section-2.10.4
fn validate_uniform_parameters<T>(&self,
uniform: Option<&WebGLUniformLocation>,
type_: UniformType,
uniform_type: UniformSetterType,
data: Option<&[T]>) -> bool {
let uniform = match uniform {
Some(uniform) => uniform,
@ -232,8 +193,8 @@ impl WebGLRenderingContext {
};
let program = self.current_program.get();
let program = match program {
Some(ref program) if program.id() == uniform.program_id() => program,
match program {
Some(ref program) if program.id() == uniform.program_id() => {},
_ => {
self.webgl_error(InvalidOperation);
return false;
@ -248,28 +209,200 @@ impl WebGLRenderingContext {
},
};
// TODO(autrilla): Don't request this every time, cache it
let active_uniform = match program.get_active_uniform(
uniform.id() as u32) {
Ok(active_uniform) => active_uniform,
Err(_) => {
self.webgl_error(InvalidOperation);
return false;
},
};
if data.len() % type_.element_count() != 0 ||
(data.len() / type_.element_count() > active_uniform.Size() as usize) {
self.webgl_error(InvalidOperation);
return false;
}
if type_.as_gl_constant() != active_uniform.Type() {
// TODO(emilio): Get more complex uniform info from ANGLE, and use it to
// properly validate that the uniform setter type is compatible with the
// uniform type, and that the uniform size matches.
if data.len() % uniform_type.element_count() != 0 {
self.webgl_error(InvalidOperation);
return false;
}
return true;
true
}
fn validate_tex_image_parameters(&self,
target: u32,
level: i32,
internal_format: u32,
width: i32,
height: i32,
border: i32,
format: u32,
data_type: u32) -> bool {
// GL_INVALID_ENUM is generated if target is not GL_TEXTURE_2D,
// GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
// GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
// GL_TEXTURE_CUBE_MAP_POSITIVE_Z, or GL_TEXTURE_CUBE_MAP_NEGATIVE_Z.
let texture = match target {
constants::TEXTURE_2D
=> self.bound_texture_2d.get(),
constants::TEXTURE_CUBE_MAP_POSITIVE_X |
constants::TEXTURE_CUBE_MAP_NEGATIVE_X |
constants::TEXTURE_CUBE_MAP_POSITIVE_Y |
constants::TEXTURE_CUBE_MAP_NEGATIVE_Y |
constants::TEXTURE_CUBE_MAP_POSITIVE_Z |
constants::TEXTURE_CUBE_MAP_NEGATIVE_Z
=> self.bound_texture_cube_map.get(),
_ => {
self.webgl_error(InvalidEnum);
return false;
},
};
// If an attempt is made to call this function with no
// WebGLTexture bound, an INVALID_OPERATION error is generated.
if texture.is_none() {
self.webgl_error(InvalidOperation);
return false;
}
// GL_INVALID_ENUM is generated if data_type is not an accepted value.
match data_type {
constants::UNSIGNED_BYTE |
constants::UNSIGNED_SHORT_4_4_4_4 |
constants::UNSIGNED_SHORT_5_5_5_1 |
constants::UNSIGNED_SHORT_5_6_5 => {},
_ => {
self.webgl_error(InvalidEnum);
return false;
},
}
// TODO(emilio): GL_INVALID_VALUE may be generated if
// level is greater than log_2(max), where max is
// the returned value of GL_MAX_TEXTURE_SIZE when
// target is GL_TEXTURE_2D or GL_MAX_CUBE_MAP_TEXTURE_SIZE
// when target is not GL_TEXTURE_2D.
let is_cubic = target != constants::TEXTURE_2D;
// GL_INVALID_VALUE is generated if target is one of the
// six cube map 2D image targets and the width and height
// parameters are not equal.
if is_cubic && width != height {
self.webgl_error(InvalidValue);
return false;
}
// GL_INVALID_VALUE is generated if internal_format is not an
// accepted format.
match internal_format {
constants::DEPTH_COMPONENT |
constants::ALPHA |
constants::RGB |
constants::RGBA |
constants::LUMINANCE |
constants::LUMINANCE_ALPHA => {},
_ => {
self.webgl_error(InvalidValue);
return false;
},
}
// GL_INVALID_OPERATION is generated if format does not
// match internal_format.
if format != internal_format {
self.webgl_error(InvalidOperation);
return false;
}
// GL_INVALID_VALUE is generated if level is less than 0.
//
// GL_INVALID_VALUE is generated if width or height is less than 0
// or greater than GL_MAX_TEXTURE_SIZE when target is GL_TEXTURE_2D or
// GL_MAX_CUBE_MAP_TEXTURE_SIZE when target is not GL_TEXTURE_2D.
//
// TODO(emilio): Check limits
if width < 0 || height < 0 || level < 0 {
self.webgl_error(InvalidValue);
return false;
}
// GL_INVALID_VALUE is generated if level is greater than zero and the
// texture is not power of two.
if level > 0 &&
(!(width as u32).is_power_of_two() ||
!(height as u32).is_power_of_two()) {
self.webgl_error(InvalidValue);
return false;
}
// GL_INVALID_VALUE is generated if border is not 0.
if border != 0 {
self.webgl_error(InvalidValue);
return false;
}
// GL_INVALID_OPERATION is generated if type is GL_UNSIGNED_SHORT_4_4_4_4 or
// GL_UNSIGNED_SHORT_5_5_5_1 and format is not GL_RGBA.
//
// GL_INVALID_OPERATION is generated if type is
// GL_UNSIGNED_SHORT_5_6_5 and format is not GL_RGB.
match data_type {
constants::UNSIGNED_SHORT_4_4_4_4 |
constants::UNSIGNED_SHORT_5_5_5_1 if format != constants::RGBA => {
self.webgl_error(InvalidOperation);
return false;
},
constants::UNSIGNED_SHORT_5_6_5 if format != constants::RGB => {
self.webgl_error(InvalidOperation);
return false;
},
_ => {},
}
true
}
fn tex_image_2d(&self,
target: u32,
level: i32,
internal_format: u32,
width: i32,
height: i32,
border: i32,
format: u32,
data_type: u32,
pixels: Vec<u8>) { // NB: pixels should NOT be premultipied
// This should be validated before reaching this function
debug_assert!(self.validate_tex_image_parameters(target, level,
internal_format,
width, height,
border, format,
data_type));
let slot = match target {
constants::TEXTURE_2D
=> self.bound_texture_2d.get(),
_ => self.bound_texture_cube_map.get(),
};
let texture = slot.as_ref().expect("No bound texture found after validation");
if format == constants::RGBA &&
data_type == constants::UNSIGNED_BYTE &&
self.texture_unpacking_settings.get().contains(PREMULTIPLY_ALPHA) {
// TODO(emilio): premultiply here.
}
// TODO(emilio): Flip Y axis if necessary here
// TexImage2D depth is always equal to 1
handle_potential_webgl_error!(self, texture.initialize(target,
width as u32,
height as u32, 1,
internal_format,
level as u32));
// TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested
let msg = WebGLCommand::TexImage2D(target, level, internal_format as i32,
width, height, format, data_type, pixels);
self.ipc_renderer
.send(CanvasMsg::WebGL(msg))
.unwrap()
}
}
@ -483,6 +616,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
Err(e) => return self.webgl_error(e),
}
} else {
slot.set(None);
// Unbind the current buffer
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::BindBuffer(target, 0)))
@ -526,7 +660,6 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
let slot = match target {
constants::TEXTURE_2D => &self.bound_texture_2d,
constants::TEXTURE_CUBE_MAP => &self.bound_texture_cube_map,
_ => return self.webgl_error(InvalidEnum),
};
@ -566,20 +699,24 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
constants::ELEMENT_ARRAY_BUFFER => self.bound_buffer_element_array.get(),
_ => return self.webgl_error(InvalidEnum),
};
let bound_buffer = match bound_buffer {
Some(bound_buffer) => bound_buffer,
None => return self.webgl_error(InvalidValue),
};
match usage {
constants::STREAM_DRAW |
constants::STATIC_DRAW |
constants::DYNAMIC_DRAW => (),
_ => return self.webgl_error(InvalidEnum),
}
let data = match data {
Some(data) => data,
None => return self.webgl_error(InvalidValue),
};
if let Some(data_vec) = array_buffer_view_to_vec::<u8>(data) {
handle_potential_webgl_error!(self, bound_buffer.buffer_data(target, &data_vec, usage));
} else {
@ -1109,7 +1246,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform1f(&self,
uniform: Option<&WebGLUniformLocation>,
val: f32) {
if self.validate_uniform_parameters(uniform, UniformType::Float, Some(&[val])) {
if self.validate_uniform_parameters(uniform, UniformSetterType::Float, Some(&[val])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform1f(uniform.unwrap().id(), val)))
.unwrap()
@ -1120,7 +1257,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform1i(&self,
uniform: Option<&WebGLUniformLocation>,
val: i32) {
if self.validate_uniform_parameters(uniform, UniformType::Int, Some(&[val])) {
if self.validate_uniform_parameters(uniform, UniformSetterType::Int, Some(&[val])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform1i(uniform.unwrap().id(), val)))
.unwrap()
@ -1133,7 +1270,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<i32>(d));
if self.validate_uniform_parameters(uniform, UniformType::Int, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform, UniformSetterType::Int, data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform1iv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1146,7 +1283,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<f32>(d));
if self.validate_uniform_parameters(uniform, UniformType::Float, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform, UniformSetterType::Float, data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform1fv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1157,7 +1294,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform2f(&self,
uniform: Option<&WebGLUniformLocation>,
x: f32, y: f32) {
if self.validate_uniform_parameters(uniform, UniformType::FloatVec2, Some(&[x, y])) {
if self.validate_uniform_parameters(uniform, UniformSetterType::FloatVec2, Some(&[x, y])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform2f(uniform.unwrap().id(), x, y)))
.unwrap()
@ -1170,7 +1307,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<f32>(d));
if self.validate_uniform_parameters(uniform, UniformType::FloatVec2, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::FloatVec2,
data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform2fv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1181,7 +1320,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform2i(&self,
uniform: Option<&WebGLUniformLocation>,
x: i32, y: i32) {
if self.validate_uniform_parameters(uniform, UniformType::IntVec2, Some(&[x, y])) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::IntVec2,
Some(&[x, y])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform2i(uniform.unwrap().id(), x, y)))
.unwrap()
@ -1194,7 +1335,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<i32>(d));
if self.validate_uniform_parameters(uniform, UniformType::IntVec2, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::IntVec2,
data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform2iv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1205,7 +1348,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform3f(&self,
uniform: Option<&WebGLUniformLocation>,
x: f32, y: f32, z: f32) {
if self.validate_uniform_parameters(uniform, UniformType::FloatVec3, Some(&[x, y, z])) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::FloatVec3,
Some(&[x, y, z])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform3f(uniform.unwrap().id(), x, y, z)))
.unwrap()
@ -1218,7 +1363,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<f32>(d));
if self.validate_uniform_parameters(uniform, UniformType::FloatVec3, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::FloatVec3,
data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform3fv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1229,7 +1376,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform3i(&self,
uniform: Option<&WebGLUniformLocation>,
x: i32, y: i32, z: i32) {
if self.validate_uniform_parameters(uniform, UniformType::IntVec3, Some(&[x, y, z])) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::IntVec3,
Some(&[x, y, z])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform3i(uniform.unwrap().id(), x, y, z)))
.unwrap()
@ -1242,7 +1391,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<i32>(d));
if self.validate_uniform_parameters(uniform, UniformType::IntVec3, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::IntVec3,
data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform3iv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1253,7 +1404,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform4i(&self,
uniform: Option<&WebGLUniformLocation>,
x: i32, y: i32, z: i32, w: i32) {
if self.validate_uniform_parameters(uniform, UniformType::IntVec4, Some(&[x, y, z, w])) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::IntVec4,
Some(&[x, y, z, w])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform4i(uniform.unwrap().id(), x, y, z, w)))
.unwrap()
@ -1267,7 +1420,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<i32>(d));
if self.validate_uniform_parameters(uniform, UniformType::IntVec4, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::IntVec4,
data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform4iv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1278,7 +1433,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
fn Uniform4f(&self,
uniform: Option<&WebGLUniformLocation>,
x: f32, y: f32, z: f32, w: f32) {
if self.validate_uniform_parameters(uniform, UniformType::FloatVec4, Some(&[x, y, z, w])) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::FloatVec4,
Some(&[x, y, z, w])) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform4f(uniform.unwrap().id(), x, y, z, w)))
.unwrap()
@ -1291,7 +1448,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
uniform: Option<&WebGLUniformLocation>,
data: Option<*mut JSObject>) {
let data_vec = data.and_then(|d| array_buffer_view_to_vec::<f32>(d));
if self.validate_uniform_parameters(uniform, UniformType::FloatVec4, data_vec.as_ref().map(Vec::as_slice)) {
if self.validate_uniform_parameters(uniform,
UniformSetterType::FloatVec4,
data_vec.as_ref().map(Vec::as_slice)) {
self.ipc_renderer
.send(CanvasMsg::WebGL(WebGLCommand::Uniform4fv(uniform.unwrap().id(), data_vec.unwrap())))
.unwrap()
@ -1400,28 +1559,112 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
.unwrap()
}
#[allow(unsafe_code)]
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
fn TexImage2D(&self,
_cx: *mut JSContext,
target: u32,
level: i32,
internal_format: u32,
width: i32,
height: i32,
border: i32,
format: u32,
data_type: u32,
source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) {
let texture = match target {
constants::TEXTURE_2D => self.bound_texture_2d.get(),
constants::TEXTURE_CUBE_MAP => self.bound_texture_cube_map.get(),
_ => return self.webgl_error(InvalidEnum),
data: Option<*mut JSObject>) {
if !self.validate_tex_image_parameters(target,
level,
internal_format,
width, height,
border,
format,
data_type) {
return; // Error handled in validate()
}
// TODO(emilio, #10693): Add type-safe wrappers to validations
let (element_size, components_per_element) = match data_type {
constants::UNSIGNED_BYTE => (1, 1),
constants::UNSIGNED_SHORT_5_6_5 => (2, 3),
constants::UNSIGNED_SHORT_5_5_5_1 |
constants::UNSIGNED_SHORT_4_4_4_4 => (2, 4),
_ => unreachable!(), // previously validated
};
if texture.is_none() {
let components = match format {
constants::DEPTH_COMPONENT => 1,
constants::ALPHA => 1,
constants::LUMINANCE => 1,
constants::LUMINANCE_ALPHA => 2,
constants::RGB => 3,
constants::RGBA => 4,
_ => unreachable!(), // previously validated
};
// If data is non-null, the type of pixels must match the type of the
// data to be read.
// 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.
// If the types do not match, an INVALID_OPERATION error is generated.
let received_size = if let Some(data) = data {
if unsafe { array_buffer_view_data_checked::<u16>(data).is_some() } {
2
} else if unsafe { array_buffer_view_data_checked::<u8>(data).is_some() } {
1
} else {
return self.webgl_error(InvalidOperation);
}
} else {
element_size
};
if received_size != element_size {
return self.webgl_error(InvalidOperation);
}
// TODO(emilio): Validate more parameters
// NOTE: width and height are positive or zero due to validate()
let expected_byte_length = width * height * element_size * components / components_per_element;
// If data is null, a buffer of sufficient size
// initialized to 0 is passed.
let buff = if let Some(data) = data {
array_buffer_view_to_vec::<u8>(data)
.expect("Can't reach here without being an ArrayBufferView!")
} else {
vec![0u8; expected_byte_length as usize]
};
if buff.len() != expected_byte_length as usize {
return self.webgl_error(InvalidOperation);
}
self.tex_image_2d(target, level,
internal_format,
width, height, border,
format, data_type, buff)
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
fn TexImage2D_(&self,
target: u32,
level: i32,
internal_format: u32,
format: u32,
data_type: u32,
source: Option<ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement>) {
let source = match source {
Some(s) => s,
None => return,
};
// NOTE: Getting the pixels probably can be short-circuited if some
// parameter is invalid.
//
// Nontheless, since it's the error case, I'm not totally sure the
// complexity is worth it.
let (pixels, size) = match source {
ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::ImageData(image_data) => {
let global = self.global();
@ -1443,7 +1686,10 @@ impl WebGLRenderingContextMethods for 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.
// TODO(emilio): Validate that the format argument
// is coherent with the image.
//
// RGB8 should be easy to support too
let mut data = match img.format {
PixelFormat::RGBA8 => img.bytes.to_vec(),
@ -1454,8 +1700,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
(data, size)
},
// TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, but
// we need to refactor it moving it to `HTMLCanvasElement` and supporting WebGLContext
// TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D,
// but we need to refactor it moving it to `HTMLCanvasElement` and support
// WebGLContext (probably via GetPixels()).
ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement::HTMLCanvasElement(canvas) => {
let canvas = canvas.r();
if let Some((mut data, size)) = canvas.fetch_all_data() {
@ -1469,25 +1716,17 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
=> unimplemented!(),
};
if size.width < 0 || size.height < 0 || level < 0 {
self.webgl_error(WebGLError::InvalidOperation);
// NB: Border must be zero
if !self.validate_tex_image_parameters(target, level, internal_format,
size.width, size.height, 0,
format, data_type) {
return; // Error handled in validate()
}
// TODO(emilio): Invert axis, convert colorspace, premultiply alpha if requested
let msg = WebGLCommand::TexImage2D(target, level, internal_format as i32,
size.width, size.height,
format, data_type, pixels);
// depth is always 1 when coming from html elements
handle_potential_webgl_error!(self, texture.unwrap().initialize(size.width as u32,
size.height as u32,
1,
internal_format,
level as u32));
self.ipc_renderer
.send(CanvasMsg::WebGL(msg))
.unwrap()
self.tex_image_2d(target, level,
internal_format,
size.width, size.height, 0,
format, data_type, pixels)
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
@ -1512,3 +1751,62 @@ impl LayoutCanvasWebGLRenderingContextHelpers for LayoutJS<WebGLRenderingContext
(*self.unsafe_get()).ipc_renderer.clone()
}
}
#[derive(Debug, PartialEq)]
pub enum UniformSetterType {
Int,
IntVec2,
IntVec3,
IntVec4,
Float,
FloatVec2,
FloatVec3,
FloatVec4,
}
impl UniformSetterType {
pub fn element_count(&self) -> usize {
match *self {
UniformSetterType::Int => 1,
UniformSetterType::IntVec2 => 2,
UniformSetterType::IntVec3 => 3,
UniformSetterType::IntVec4 => 4,
UniformSetterType::Float => 1,
UniformSetterType::FloatVec2 => 2,
UniformSetterType::FloatVec3 => 3,
UniformSetterType::FloatVec4 => 4,
}
}
pub fn is_compatible_with(&self, gl_type: u32) -> bool {
gl_type == self.as_gl_constant() || match *self {
// Sampler uniform variables have an index value (the index of the
// texture), and as such they have to be set as ints
UniformSetterType::Int => gl_type == constants::SAMPLER_2D ||
gl_type == constants::SAMPLER_CUBE,
// Don't ask me why, but it seems we must allow setting bool
// uniforms with uniform1f.
//
// See the WebGL conformance test
// conformance/uniforms/gl-uniform-bool.html
UniformSetterType::Float => gl_type == constants::BOOL,
UniformSetterType::FloatVec2 => gl_type == constants::BOOL_VEC2,
UniformSetterType::FloatVec3 => gl_type == constants::BOOL_VEC3,
UniformSetterType::FloatVec4 => gl_type == constants::BOOL_VEC4,
_ => false,
}
}
fn as_gl_constant(&self) -> u32 {
match *self {
UniformSetterType::Int => constants::INT,
UniformSetterType::IntVec2 => constants::INT_VEC2,
UniformSetterType::IntVec3 => constants::INT_VEC3,
UniformSetterType::IntVec4 => constants::INT_VEC4,
UniformSetterType::Float => constants::FLOAT,
UniformSetterType::FloatVec2 => constants::FLOAT_VEC2,
UniformSetterType::FloatVec3 => constants::FLOAT_VEC3,
UniformSetterType::FloatVec4 => constants::FLOAT_VEC4,
}
}
}