Implement instanced WebGL drawing calls (part of #20791)

This commit is contained in:
Anthony Ramine 2018-03-20 10:11:46 +01:00
parent 0aefffc5bf
commit 4e6eea221a
12 changed files with 432 additions and 125 deletions

View file

@ -937,6 +937,34 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
) -> Option<Vec<DomRoot<WebGLShader>>> {
self.base.GetAttachedShaders(program)
}
/// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
fn DrawArraysInstanced(
&self,
mode: u32,
first: i32,
count: i32,
primcount: i32,
) {
self.base.draw_arrays_instanced(mode, first, count, primcount);
}
/// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
fn DrawElementsInstanced(
&self,
mode: u32,
count: i32,
type_: u32,
offset: i64,
primcount: i32,
) {
self.base.draw_elements_instanced(mode, count, type_, offset, primcount);
}
/// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
fn VertexAttribDivisor(&self, index: u32, divisor: u32) {
self.base.vertex_attrib_divisor(index, divisor);
}
}

View file

@ -0,0 +1,92 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use canvas_traits::webgl::WebGLVersion;
use dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding;
use dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants;
use dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysMethods;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::{Dom, DomRoot};
use dom::webglrenderingcontext::WebGLRenderingContext;
use dom_struct::dom_struct;
use super::{WebGLExtension, WebGLExtensions, WebGLExtensionSpec};
#[dom_struct]
pub struct ANGLEInstancedArrays {
reflector_: Reflector,
ctx: Dom<WebGLRenderingContext>,
}
impl ANGLEInstancedArrays {
fn new_inherited(ctx: &WebGLRenderingContext) -> Self {
Self {
reflector_: Reflector::new(),
ctx: Dom::from_ref(ctx),
}
}
}
impl WebGLExtension for ANGLEInstancedArrays {
type Extension = Self;
fn new(ctx: &WebGLRenderingContext) -> DomRoot<Self> {
reflect_dom_object(
Box::new(ANGLEInstancedArrays::new_inherited(ctx)),
&*ctx.global(),
ANGLEInstancedArraysBinding::Wrap,
)
}
fn spec() -> WebGLExtensionSpec {
WebGLExtensionSpec::Specific(WebGLVersion::WebGL1)
}
fn is_supported(ext: &WebGLExtensions) -> bool {
ext.supports_any_gl_extension(&[
"GL_ANGLE_instanced_arrays",
"GL_ARB_instanced_arrays",
"GL_EXT_instanced_arrays",
"GL_NV_instanced_arrays",
])
}
fn enable(ext: &WebGLExtensions) {
ext.enable_get_vertex_attrib_name(
ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE,
);
}
fn name() -> &'static str {
"ANGLE_instanced_arrays"
}
}
impl ANGLEInstancedArraysMethods for ANGLEInstancedArrays {
// https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
fn DrawArraysInstancedANGLE(
&self,
mode: u32,
first: i32,
count: i32,
primcount: i32,
) {
self.ctx.draw_arrays_instanced(mode, first, count, primcount);
}
// https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
fn DrawElementsInstancedANGLE(
&self,
mode: u32,
count: i32,
type_: u32,
offset: i64,
primcount: i32,
) {
self.ctx.draw_elements_instanced(mode, count, type_, offset, primcount);
}
fn VertexAttribDivisorANGLE(&self, index: u32, divisor: u32) {
self.ctx.vertex_attrib_divisor(index, divisor);
}
}

View file

@ -5,6 +5,7 @@
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use super::{ext_constants, WebGLExtension, WebGLExtensions, WebGLExtensionSpec};
pub mod angleinstancedarrays;
pub mod extblendminmax;
pub mod extshadertexturelod;
pub mod exttexturefilteranisotropic;

View file

@ -4,6 +4,7 @@
use canvas_traits::webgl::{WebGLError, WebGLVersion};
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants;
use dom::bindings::codegen::Bindings::EXTTextureFilterAnisotropicBinding::EXTTextureFilterAnisotropicConstants;
use dom::bindings::codegen::Bindings::OESStandardDerivativesBinding::OESStandardDerivativesConstants;
use dom::bindings::codegen::Bindings::OESTextureHalfFloatBinding::OESTextureHalfFloatConstants;
@ -54,6 +55,13 @@ const DEFAULT_DISABLED_GET_TEX_PARAMETER_NAMES_WEBGL1: [GLenum; 1] = [
EXTTextureFilterAnisotropicConstants::TEXTURE_MAX_ANISOTROPY_EXT,
];
// Param names that are implemented for glGetVertexAttrib in a WebGL 1.0 context
// but must trigger a InvalidEnum error until the related WebGL Extensions are enabled.
// Example: https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
const DEFAULT_DISABLED_GET_VERTEX_ATTRIB_NAMES_WEBGL1: [GLenum; 1] = [
ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE,
];
/// WebGL features that are enabled/disabled by WebGL Extensions.
#[derive(JSTraceable, MallocSizeOf)]
struct WebGLExtensionFeatures {
@ -68,6 +76,8 @@ struct WebGLExtensionFeatures {
disabled_get_parameter_names: FnvHashSet<GLenum>,
/// WebGL GetTexParameter() names enabled by extensions.
disabled_get_tex_parameter_names: FnvHashSet<GLenum>,
/// WebGL GetAttribVertex() names enabled by extensions.
disabled_get_vertex_attrib_names: FnvHashSet<GLenum>,
/// WebGL OES_element_index_uint extension.
element_index_uint_enabled: bool,
/// WebGL EXT_blend_minmax extension.
@ -80,6 +90,7 @@ impl WebGLExtensionFeatures {
disabled_tex_types,
disabled_get_parameter_names,
disabled_get_tex_parameter_names,
disabled_get_vertex_attrib_names,
element_index_uint_enabled,
blend_minmax_enabled,
) = match webgl_version {
@ -88,12 +99,20 @@ impl WebGLExtensionFeatures {
DEFAULT_DISABLED_TEX_TYPES_WEBGL1.iter().cloned().collect(),
DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL1.iter().cloned().collect(),
DEFAULT_DISABLED_GET_TEX_PARAMETER_NAMES_WEBGL1.iter().cloned().collect(),
DEFAULT_DISABLED_GET_VERTEX_ATTRIB_NAMES_WEBGL1.iter().cloned().collect(),
false,
false,
)
},
WebGLVersion::WebGL2 => {
(Default::default(), Default::default(), Default::default(), true, true)
(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
true,
true,
)
}
};
Self {
@ -105,6 +124,7 @@ impl WebGLExtensionFeatures {
hint_targets: Default::default(),
disabled_get_parameter_names,
disabled_get_tex_parameter_names,
disabled_get_vertex_attrib_names,
element_index_uint_enabled,
blend_minmax_enabled,
}
@ -269,7 +289,16 @@ impl WebGLExtensions {
!self.features.borrow().disabled_get_tex_parameter_names.contains(&name)
}
pub fn enable_get_vertex_attrib_name(&self, name: GLenum) {
self.features.borrow_mut().disabled_get_vertex_attrib_names.remove(&name);
}
pub fn is_get_vertex_attrib_name_enabled(&self, name: GLenum) -> bool {
!self.features.borrow().disabled_get_vertex_attrib_names.contains(&name)
}
fn register_all_extensions(&self) {
self.register::<ext::angleinstancedarrays::ANGLEInstancedArrays>();
self.register::<ext::extblendminmax::EXTBlendMinmax>();
self.register::<ext::extshadertexturelod::EXTShaderTextureLod>();
self.register::<ext::exttexturefilteranisotropic::EXTTextureFilterAnisotropic>();

View file

@ -12,6 +12,7 @@ use canvas_traits::webgl::{WebGLResult, WebGLSLVersion, WebGLVersion};
use canvas_traits::webgl::{WebVRCommand, webgl_channel};
use canvas_traits::webgl::WebGLError::*;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::ANGLEInstancedArraysBinding::ANGLEInstancedArraysConstants;
use dom::bindings::codegen::Bindings::EXTBlendMinmaxBinding::EXTBlendMinmaxConstants;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes};
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
@ -251,7 +252,7 @@ impl WebGLRenderingContext {
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)),
extension_manager: WebGLExtensions::new(webgl_version)
extension_manager: WebGLExtensions::new(webgl_version),
}
})
}
@ -1156,6 +1157,142 @@ impl WebGLRenderingContext {
}
}
// https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
pub fn draw_arrays_instanced(
&self,
mode: u32,
first: i32,
count: i32,
primcount: i32,
) {
match mode {
constants::POINTS | constants::LINE_STRIP |
constants::LINE_LOOP | constants::LINES |
constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN |
constants::TRIANGLES => {},
_ => {
return self.webgl_error(InvalidEnum);
}
}
if first < 0 || count < 0 || primcount < 0 {
return self.webgl_error(InvalidValue);
}
let current_program = handle_potential_webgl_error!(
self,
self.current_program.get().ok_or(InvalidOperation),
return
);
let required_len = if count > 0 {
handle_potential_webgl_error!(
self,
first.checked_add(count).map(|len| len as u32).ok_or(InvalidOperation),
return
)
} else {
0
};
handle_potential_webgl_error!(
self,
self.vertex_attribs.validate_for_draw(required_len, primcount as u32, &current_program.active_attribs()),
return
);
if !self.validate_framebuffer_complete() {
return;
}
self.send_command(
WebGLCommand::DrawArraysInstanced { mode, first, count, primcount },
);
self.mark_as_dirty();
}
// https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
pub fn draw_elements_instanced(
&self,
mode: u32,
count: i32,
type_: u32,
offset: i64,
primcount: i32,
) {
match mode {
constants::POINTS | constants::LINE_STRIP |
constants::LINE_LOOP | constants::LINES |
constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN |
constants::TRIANGLES => {},
_ => {
return self.webgl_error(InvalidEnum);
}
}
if count < 0 || offset < 0 || primcount < 0 {
return self.webgl_error(InvalidValue);
}
let type_size = match type_ {
constants::UNSIGNED_BYTE => 1,
constants::UNSIGNED_SHORT => 2,
_ => return self.webgl_error(InvalidEnum),
};
if offset % type_size != 0 {
return self.webgl_error(InvalidOperation);
}
let current_program = handle_potential_webgl_error!(
self,
self.current_program.get().ok_or(InvalidOperation),
return
);
if count > 0 && primcount > 0 {
if let Some(array_buffer) = self.bound_buffer_element_array.get() {
// WebGL Spec: check buffer overflows, must be a valid multiple of the size.
let val = offset as u64 + (count as u64 * type_size as u64);
if val > array_buffer.capacity() as u64 {
return self.webgl_error(InvalidOperation);
}
} else {
// From the WebGL spec
//
// a non-null WebGLBuffer must be bound to the ELEMENT_ARRAY_BUFFER binding point
// or an INVALID_OPERATION error will be generated.
//
return self.webgl_error(InvalidOperation);
}
}
// TODO(nox): Pass the correct number of vertices required.
handle_potential_webgl_error!(
self,
self.vertex_attribs.validate_for_draw(0, primcount as u32, &current_program.active_attribs()),
return
);
if !self.validate_framebuffer_complete() {
return;
}
self.send_command(WebGLCommand::DrawElementsInstanced {
mode,
count,
type_,
offset: offset as u32,
primcount,
});
self.mark_as_dirty();
}
pub fn vertex_attrib_divisor(&self, index: u32, divisor: u32) {
if index >= self.limits.max_vertex_attribs {
return self.webgl_error(InvalidValue);
}
self.vertex_attribs.set_divisor(index, divisor);
self.send_command(WebGLCommand::VertexAttribDivisor { index, divisor });
}
// Used by HTMLCanvasElement.toDataURL
//
// This emits errors quite liberally, but the spec says that this operation
@ -2248,7 +2385,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
handle_potential_webgl_error!(
self,
self.vertex_attribs.validate_for_draw(required_len, &current_program.active_attribs()),
self.vertex_attribs.validate_for_draw(required_len, 1, &current_program.active_attribs()),
return
);
@ -2318,7 +2455,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
// TODO(nox): Pass the correct number of vertices required.
handle_potential_webgl_error!(
self,
self.vertex_attribs.validate_for_draw(0, &current_program.active_attribs()),
self.vertex_attribs.validate_for_draw(0, 1, &current_program.active_attribs()),
return
);
@ -2661,6 +2798,11 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return ObjectValue(result.get());
}
if !self.extension_manager.is_get_vertex_attrib_name_enabled(param) {
self.webgl_error(WebGLError::InvalidEnum);
return NullValue();
}
match param {
constants::VERTEX_ATTRIB_ARRAY_ENABLED => BooleanValue(data.enabled_as_array),
constants::VERTEX_ATTRIB_ARRAY_SIZE => Int32Value(data.size as i32),
@ -2676,6 +2818,9 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
}
jsval.get()
}
ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE => {
Int32Value(data.divisor as i32)
}
_ => {
self.webgl_error(InvalidEnum);
NullValue()
@ -3903,6 +4048,7 @@ impl VertexAttribs {
stride: stride as u8,
offset: offset as u32,
buffer: Some(Dom::from_ref(buffer)),
divisor: data.divisor,
};
Ok(())
}
@ -3927,33 +4073,51 @@ impl VertexAttribs {
self.attribs.borrow_mut()[index as usize].enabled_as_array = value;
}
fn set_divisor(&self, index: u32, value: u32) {
self.attribs.borrow_mut()[index as usize].divisor = value;
}
fn validate_for_draw(
&self,
required_len: u32,
instance_count: u32,
active_attribs: &[ActiveAttribInfo],
) -> WebGLResult<()> {
// TODO(nox): Cache limits per VAO.
let attribs = self.attribs.borrow();
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.2
if attribs.iter().any(|data| data.enabled_as_array && data.buffer.is_none()) {
return Err(InvalidOperation);
}
if required_len == 0 {
return Ok(());
}
// TODO(nox): Cache that per VAO.
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.6
let mut has_active_attrib = false;
let mut has_divisor_0 = false;
for active_info in active_attribs {
if active_info.location < 0 {
continue;
}
has_active_attrib = true;
let attrib = &attribs[active_info.location as usize];
if attrib.divisor == 0 {
has_divisor_0 = true;
}
if !attrib.enabled_as_array {
continue;
}
if attrib.max_vertices() < required_len {
return Err(InvalidOperation);
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.6
if required_len > 0 && instance_count > 0 {
let max_vertices = attrib.max_vertices();
if attrib.divisor == 0 {
if max_vertices < required_len {
return Err(InvalidOperation);
}
} else if max_vertices.checked_mul(attrib.divisor).map_or(false, |v| v < instance_count) {
return Err(InvalidOperation);
}
}
}
if has_active_attrib && !has_divisor_0 {
return Err(InvalidOperation);
}
Ok(())
}
}
@ -3969,6 +4133,7 @@ pub struct VertexAttribData {
stride: u8,
offset: u32,
buffer: Option<Dom<WebGLBuffer>>,
divisor: u32,
}
impl Default for VertexAttribData {
@ -3983,6 +4148,7 @@ impl Default for VertexAttribData {
stride: 0,
offset: 0,
buffer: None,
divisor: 0,
}
}
}

View file

@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* WebGL IDL definitions from the Khronos specification:
* https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
*/
[NoInterfaceObject]
interface ANGLEInstancedArrays {
const GLenum VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE = 0x88FE;
void drawArraysInstancedANGLE(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
void drawElementsInstancedANGLE(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei primcount);
void vertexAttribDivisorANGLE(GLuint index, GLuint divisor);
};

View file

@ -502,9 +502,9 @@ interface WebGL2RenderingContextBase
// void vertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset);
/* Writing to the drawing buffer */
// void vertexAttribDivisor(GLuint index, GLuint divisor);
// void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);
// void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount);
void vertexAttribDivisor(GLuint index, GLuint divisor);
void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);
void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount);
// void drawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset);
/* Reading back pixels */