Always use a WebGLVertexArrayObject to handle vertex attribs

This lets us clean up how buffers are reference-counted.
This commit is contained in:
Anthony Ramine 2018-08-01 13:44:09 +02:00
parent 3e09d7270a
commit fba6d801a1
6 changed files with 442 additions and 561 deletions

View file

@ -2,24 +2,20 @@
* 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::{webgl_channel, WebGLCommand, WebGLError, WebGLVersion};
use canvas_traits::webgl::WebGLVersion;
use dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::{self, OESVertexArrayObjectMethods};
use dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use dom::bindings::root::{Dom, DomRoot};
use dom::webglrenderingcontext::WebGLRenderingContext;
use dom::webglvertexarrayobjectoes::WebGLVertexArrayObjectOES;
use dom_struct::dom_struct;
use js::conversions::ToJSValConvertible;
use js::jsapi::JSContext;
use js::jsval::{JSVal, NullValue};
use super::{WebGLExtension, WebGLExtensions, WebGLExtensionSpec};
#[dom_struct]
pub struct OESVertexArrayObject {
reflector_: Reflector,
ctx: Dom<WebGLRenderingContext>,
bound_vao: MutNullableDom<WebGLVertexArrayObjectOES>,
}
impl OESVertexArrayObject {
@ -27,104 +23,29 @@ impl OESVertexArrayObject {
Self {
reflector_: Reflector::new(),
ctx: Dom::from_ref(ctx),
bound_vao: MutNullableDom::new(None)
}
}
#[allow(unsafe_code)]
fn get_current_binding(&self, cx:*mut JSContext) -> JSVal {
rooted!(in(cx) let mut rval = NullValue());
if let Some(bound_vao) = self.bound_vao.get() {
unsafe {
bound_vao.to_jsval(cx, rval.handle_mut());
}
}
rval.get()
}
}
impl OESVertexArrayObjectMethods for OESVertexArrayObject {
// https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
fn CreateVertexArrayOES(&self) -> Option<DomRoot<WebGLVertexArrayObjectOES>> {
let (sender, receiver) = webgl_channel().unwrap();
self.ctx.send_command(WebGLCommand::CreateVertexArray(sender));
receiver.recv().unwrap().map(|id| WebGLVertexArrayObjectOES::new(&self.ctx, id))
self.ctx.create_vertex_array()
}
// https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
fn DeleteVertexArrayOES(&self, vao: Option<&WebGLVertexArrayObjectOES>) {
if let Some(vao) = vao {
if vao.is_deleted() {
return;
}
// Unbind deleted VAO if currently bound
if let Some(bound_vao) = self.bound_vao.get() {
if bound_vao.id() == vao.id() {
self.bound_vao.set(None);
self.ctx.send_command(WebGLCommand::BindVertexArray(None));
}
}
// Remove VAO references from buffers
for attrib_data in &*vao.vertex_attribs().borrow() {
if let Some(buffer) = attrib_data.buffer() {
buffer.remove_vao_reference(vao.id());
}
}
if let Some(buffer) = vao.bound_buffer_element_array() {
buffer.remove_vao_reference(vao.id());
}
// Delete the vao
self.ctx.send_command(WebGLCommand::DeleteVertexArray(vao.id()));
vao.set_deleted();
}
self.ctx.delete_vertex_array(vao);
}
// https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
fn IsVertexArrayOES(&self, vao: Option<&WebGLVertexArrayObjectOES>) -> bool {
// Conformance tests expect false if vao never bound
vao.map_or(false, |vao| !vao.is_deleted() && vao.ever_bound())
self.ctx.is_vertex_array(vao)
}
// https://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/
fn BindVertexArrayOES(&self, vao: Option<&WebGLVertexArrayObjectOES>) {
if let Some(bound_vao) = self.bound_vao.get() {
// Store buffers attached to attrib pointers
bound_vao.vertex_attribs().clone_from(&self.ctx.vertex_attribs());
for attrib_data in &*bound_vao.vertex_attribs().borrow() {
if let Some(buffer) = attrib_data.buffer() {
buffer.add_vao_reference(bound_vao.id());
}
}
// Store element array buffer
let element_array = self.ctx.bound_buffer_element_array();
bound_vao.set_bound_buffer_element_array(element_array.as_ref().map(|buffer| {
buffer.add_vao_reference(bound_vao.id());
&**buffer
}));
}
if let Some(vao) = vao {
if vao.is_deleted() {
self.ctx.webgl_error(WebGLError::InvalidOperation);
return;
}
self.ctx.send_command(WebGLCommand::BindVertexArray(Some(vao.id())));
vao.set_ever_bound();
self.bound_vao.set(Some(&vao));
// Restore WebGLRenderingContext current bindings
self.ctx.vertex_attribs().clone_from(&vao.vertex_attribs());
let element_array = vao.bound_buffer_element_array();
self.ctx.set_bound_buffer_element_array(element_array.as_ref().map(|buffer| &**buffer));
} else {
self.ctx.send_command(WebGLCommand::BindVertexArray(None));
self.bound_vao.set(None);
self.ctx.vertex_attribs().clear();
}
self.ctx.bind_vertex_array(vao);
}
}
@ -147,18 +68,7 @@ impl WebGLExtension for OESVertexArrayObject {
}
fn enable(ext: &WebGLExtensions) {
let query = OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES;
ext.add_query_parameter_handler(query, Box::new(|cx, webgl_ctx| {
match webgl_ctx.get_extension_manager().get_dom_object::<OESVertexArrayObject>() {
Some(dom_object) => {
Ok(dom_object.get_current_binding(cx))
},
None => {
// Extension instance not found!
Err(WebGLError::InvalidOperation)
}
}
}));
ext.enable_get_parameter_name(OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES);
}
fn name() -> &'static str {

View file

@ -2,24 +2,20 @@
* 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::{WebGLError, WebGLVersion};
use canvas_traits::webgl::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;
use dom::bindings::codegen::Bindings::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::root::DomRoot;
use dom::bindings::trace::JSTraceable;
use dom::webglrenderingcontext::WebGLRenderingContext;
use fnv::{FnvHashMap, FnvHashSet};
use gleam::gl::GLenum;
use js::jsapi::JSContext;
use js::jsapi::JSObject;
use js::jsval::JSVal;
use malloc_size_of::MallocSizeOf;
use ref_filter_map::ref_filter_map;
use std::cell::Ref;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::ptr::NonNull;
@ -43,9 +39,10 @@ const DEFAULT_NOT_FILTERABLE_TEX_TYPES: [GLenum; 2] = [
// Param names that are implemented for glGetParameter 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/OES_standard_derivatives/
const DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL1: [GLenum; 2] = [
const DEFAULT_DISABLED_GET_PARAMETER_NAMES_WEBGL1: [GLenum; 3] = [
EXTTextureFilterAnisotropicConstants::MAX_TEXTURE_MAX_ANISOTROPY_EXT,
OESStandardDerivativesConstants::FRAGMENT_SHADER_DERIVATIVE_HINT_OES,
OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES,
];
// Param names that are implemented for glGetTexParameter in a WebGL 1.0 context
@ -69,7 +66,6 @@ struct WebGLExtensionFeatures {
disabled_tex_types: FnvHashSet<GLenum>,
not_filterable_tex_types: FnvHashSet<GLenum>,
effective_tex_internal_formats: FnvHashMap<TexFormatType, u32>,
query_parameter_handlers: FnvHashMap<GLenum, WebGLQueryParameterHandler>,
/// WebGL Hint() targets enabled by extensions.
hint_targets: FnvHashSet<GLenum>,
/// WebGL GetParameter() names enabled by extensions.
@ -120,7 +116,6 @@ impl WebGLExtensionFeatures {
disabled_tex_types,
not_filterable_tex_types: DEFAULT_NOT_FILTERABLE_TEX_TYPES.iter().cloned().collect(),
effective_tex_internal_formats: Default::default(),
query_parameter_handlers: Default::default(),
hint_targets: Default::default(),
disabled_get_parameter_names,
disabled_get_tex_parameter_names,
@ -196,18 +191,6 @@ impl WebGLExtensions {
self.extensions.borrow().get(&name).map_or(false, |ext| { ext.is_enabled() })
}
pub fn get_dom_object<T>(&self) -> Option<DomRoot<T::Extension>>
where
T: 'static + WebGLExtension + JSTraceable + MallocSizeOf
{
let name = T::name().to_uppercase();
self.extensions.borrow().get(&name).and_then(|extension| {
extension.as_any().downcast_ref::<TypedWebGLExtensionWrapper<T>>().and_then(|extension| {
extension.dom_object()
})
})
}
pub fn supports_gl_extension(&self, name: &str) -> bool {
self.features.borrow().gl_extensions.contains(name)
}
@ -252,19 +235,6 @@ impl WebGLExtensions {
self.features.borrow().not_filterable_tex_types.get(&text_data_type).is_none()
}
pub fn add_query_parameter_handler(&self, name: GLenum, f: Box<WebGLQueryParameterFunc>) {
let handler = WebGLQueryParameterHandler {
func: f
};
self.features.borrow_mut().query_parameter_handlers.insert(name, handler);
}
pub fn get_query_parameter_handler(&self, name: GLenum) -> Option<Ref<Box<WebGLQueryParameterFunc>>> {
ref_filter_map(self.features.borrow(), |features| {
features.query_parameter_handlers.get(&name).map(|item| &item.func)
})
}
pub fn enable_hint_target(&self, name: GLenum) {
self.features.borrow_mut().hint_targets.insert(name);
}
@ -331,14 +301,3 @@ impl WebGLExtensions {
// Helper structs
#[derive(Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
struct TexFormatType(u32, u32);
type WebGLQueryParameterFunc = Fn(*mut JSContext, &WebGLRenderingContext)
-> Result<JSVal, WebGLError>;
#[derive(MallocSizeOf)]
struct WebGLQueryParameterHandler {
#[ignore_malloc_size_of = "Closures are hard"]
func: Box<WebGLQueryParameterFunc>
}
unsafe_no_jsmanaged_fields!(WebGLQueryParameterHandler);

View file

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::reflector::DomObject;
use dom::bindings::root::{DomRoot, MutNullableDom};
use dom::bindings::root::MutNullableDom;
use dom::bindings::trace::JSTraceable;
use dom::webglrenderingcontext::WebGLRenderingContext;
use js::jsapi::JSObject;
@ -87,11 +87,3 @@ impl<T> WebGLExtensionWrapper for TypedWebGLExtensionWrapper<T>
self
}
}
impl<T> TypedWebGLExtensionWrapper<T>
where T: WebGLExtension + JSTraceable + MallocSizeOf + 'static
{
pub fn dom_object(&self) -> Option<DomRoot<T::Extension>> {
self.extension.get()
}
}

View file

@ -3,9 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use canvas_traits::webgl::{WebGLBufferId, WebGLCommand, WebGLError, WebGLResult, WebGLVertexArrayId};
use canvas_traits::webgl::{WebGLBufferId, WebGLCommand, WebGLError, WebGLResult};
use canvas_traits::webgl::webgl_channel;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::WebGLBufferBinding;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants;
use dom::bindings::inheritance::Castable;
@ -15,8 +14,6 @@ use dom::webglobject::WebGLObject;
use dom::webglrenderingcontext::WebGLRenderingContext;
use dom_struct::dom_struct;
use std::cell::Cell;
use std::collections::HashSet;
#[dom_struct]
pub struct WebGLBuffer {
@ -25,10 +22,8 @@ pub struct WebGLBuffer {
/// The target to which this buffer was bound the first time
target: Cell<Option<u32>>,
capacity: Cell<usize>,
is_deleted: Cell<bool>,
// The Vertex Array Objects that are referencing this buffer
vao_references: DomRefCell<Option<HashSet<WebGLVertexArrayId>>>,
pending_delete: Cell<bool>,
marked_for_deletion: Cell<bool>,
attached_counter: Cell<u32>,
/// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetBufferParameteriv.xml
usage: Cell<u32>,
}
@ -37,12 +32,11 @@ impl WebGLBuffer {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLBufferId) -> Self {
Self {
webgl_object: WebGLObject::new_inherited(context),
id: id,
target: Cell::new(None),
capacity: Cell::new(0),
is_deleted: Cell::new(false),
vao_references: DomRefCell::new(None),
pending_delete: Cell::new(false),
id,
target: Default::default(),
capacity: Default::default(),
marked_for_deletion: Default::default(),
attached_counter: Default::default(),
usage: Cell::new(WebGLRenderingContextConstants::STATIC_DRAW),
}
}
@ -68,24 +62,6 @@ impl WebGLBuffer {
self.id
}
// NB: Only valid buffer targets come here
pub fn bind(&self, target: u32) -> WebGLResult<()> {
if self.is_deleted() || self.is_pending_delete() {
return Err(WebGLError::InvalidOperation);
}
if let Some(previous_target) = self.target.get() {
if target != previous_target {
return Err(WebGLError::InvalidOperation);
}
} else {
self.target.set(Some(target));
}
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::BindBuffer(target, Some(self.id)));
Ok(())
}
pub fn buffer_data<T>(&self, target: u32, data: T, usage: u32) -> WebGLResult<()>
where
T: Into<Vec<u8>>,
@ -115,56 +91,59 @@ impl WebGLBuffer {
self.capacity.get()
}
pub fn delete(&self) {
if !self.is_deleted.get() {
self.is_deleted.set(true);
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::DeleteBuffer(self.id));
pub fn mark_for_deletion(&self) {
if self.marked_for_deletion.get() {
return;
}
self.marked_for_deletion.set(true);
if self.is_deleted() {
self.delete();
}
}
fn delete(&self) {
assert!(self.is_deleted());
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::DeleteBuffer(self.id));
}
pub fn is_marked_for_deletion(&self) -> bool {
self.marked_for_deletion.get()
}
pub fn is_deleted(&self) -> bool {
self.is_deleted.get()
self.marked_for_deletion.get() && !self.is_attached()
}
pub fn target(&self) -> Option<u32> {
self.target.get()
}
pub fn is_attached_to_vao(&self) -> bool {
self.vao_references.borrow().as_ref().map_or(false, |vaos| !vaos.is_empty())
}
pub fn set_pending_delete(&self) {
self.pending_delete.set(true);
}
pub fn is_pending_delete(&self) -> bool {
self.pending_delete.get()
}
pub fn add_vao_reference(&self, id: WebGLVertexArrayId) {
let mut vao_refs = self.vao_references.borrow_mut();
if let Some(ref mut vao_refs) = *vao_refs {
vao_refs.insert(id);
return;
pub fn set_target(&self, target: u32) -> WebGLResult<()> {
if self.target.get().map_or(false, |t| t != target) {
return Err(WebGLError::InvalidOperation);
}
let mut map = HashSet::new();
map.insert(id);
*vao_refs = Some(map);
self.target.set(Some(target));
Ok(())
}
pub fn remove_vao_reference(&self, id: WebGLVertexArrayId) {
if let Some(ref mut vao_refs) = *self.vao_references.borrow_mut() {
if vao_refs.take(&id).is_some() && self.pending_delete.get() {
// WebGL spec: The deleted buffers should no longer be valid when the VAOs are deleted
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::DeleteBuffer(self.id));
self.is_deleted.set(true);
}
pub fn is_attached(&self) -> bool {
self.attached_counter.get() != 0
}
pub fn increment_attached_counter(&self) {
self.attached_counter.set(
self.attached_counter.get().checked_add(1).expect("refcount overflowed"),
);
}
pub fn decrement_attached_counter(&self) {
self.attached_counter.set(
self.attached_counter.get().checked_sub(1).expect("refcount underflowed"),
);
if self.is_deleted() {
self.delete();
}
}

View file

@ -4,7 +4,7 @@
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
use canvas_traits::canvas::{byte_swap, multiply_u8_pixel};
use canvas_traits::webgl::{ActiveAttribInfo, DOMToTextureCommand, Parameter};
use canvas_traits::webgl::{DOMToTextureCommand, Parameter};
use canvas_traits::webgl::{TexParameter, WebGLCommand, WebGLContextShareMode, WebGLError};
use canvas_traits::webgl::{WebGLFramebufferBindingRequest, WebGLMsg, WebGLMsgSender};
use canvas_traits::webgl::{WebGLProgramId, WebGLResult, WebGLSLVersion, WebGLSender};
@ -13,6 +13,7 @@ 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::OESVertexArrayObjectBinding::OESVertexArrayObjectConstants;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{self, WebGLContextAttributes};
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
@ -24,7 +25,7 @@ use dom::bindings::conversions::{DerivedFrom, ToJSValConvertible};
use dom::bindings::error::{Error, ErrorResult};
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
use dom::bindings::root::{Dom, DomOnceCell, DomRoot, LayoutDom, MutNullableDom};
use dom::bindings::str::DOMString;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::htmlcanvaselement::HTMLCanvasElement;
@ -47,6 +48,7 @@ use dom::webglshader::WebGLShader;
use dom::webglshaderprecisionformat::WebGLShaderPrecisionFormat;
use dom::webgltexture::{TexParameterValue, WebGLTexture};
use dom::webgluniformlocation::WebGLUniformLocation;
use dom::webglvertexarrayobjectoes::WebGLVertexArrayObjectOES;
use dom::window::Window;
use dom_struct::dom_struct;
use euclid::Size2D;
@ -61,11 +63,10 @@ use js::typedarray::{TypedArray, TypedArrayElementCreator};
use net_traits::image::base::PixelFormat;
use net_traits::image_cache::ImageResponse;
use offscreen_gl_context::{GLContextAttributes, GLLimits};
use ref_filter_map::ref_filter_map;
use script_layout_interface::HTMLCanvasDataSource;
use serde::{Deserialize, Serialize};
use servo_config::prefs::PREFS;
use std::cell::{Cell, Ref};
use std::cell::Cell;
use std::cmp;
use std::ptr::{self, NonNull};
use webrender_api;
@ -95,17 +96,6 @@ macro_rules! handle_object_deletion {
};
}
macro_rules! object_binding_to_js_or_null {
($cx: expr, $binding:expr) => {
{
rooted!(in($cx) let mut rval = NullValue());
if let Some(bound_object) = $binding.get() {
bound_object.to_jsval($cx, rval.handle_mut());
}
rval.get()
}
};
}
macro_rules! optional_root_object_to_js_or_null {
($cx: expr, $binding:expr) => {
@ -196,9 +186,8 @@ pub struct WebGLRenderingContext {
bound_textures: DomRefCell<FnvHashMap<u32, TextureUnitBindings>>,
bound_texture_unit: Cell<u32>,
bound_buffer_array: MutNullableDom<WebGLBuffer>,
bound_buffer_element_array: MutNullableDom<WebGLBuffer>,
vertex_attribs: VertexAttribs,
current_program: MutNullableDom<WebGLProgram>,
/// https://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Vertex_Attribute_0
#[ignore_malloc_size_of = "Because it's small"]
current_vertex_attrib_0: Cell<(f32, f32, f32, f32)>,
#[ignore_malloc_size_of = "Because it's small"]
@ -207,6 +196,8 @@ pub struct WebGLRenderingContext {
current_clear_color: Cell<(f32, f32, f32, f32)>,
extension_manager: WebGLExtensions,
capabilities: Capabilities,
default_vao: DomOnceCell<WebGLVertexArrayObjectOES>,
current_vao: MutNullableDom<WebGLVertexArrayObjectOES>,
}
impl WebGLRenderingContext {
@ -239,7 +230,6 @@ impl WebGLRenderingContext {
share_mode: ctx_data.share_mode,
webgl_version,
glsl_version: ctx_data.glsl_version,
vertex_attribs: VertexAttribs::new(ctx_data.limits.max_vertex_attribs),
limits: ctx_data.limits,
canvas: Dom::from_ref(canvas),
last_error: Cell::new(None),
@ -249,7 +239,6 @@ impl WebGLRenderingContext {
bound_textures: DomRefCell::new(Default::default()),
bound_texture_unit: Cell::new(constants::TEXTURE0),
bound_buffer_array: MutNullableDom::new(None),
bound_buffer_element_array: MutNullableDom::new(None),
bound_renderbuffer: MutNullableDom::new(None),
current_program: MutNullableDom::new(None),
current_vertex_attrib_0: Cell::new((0f32, 0f32, 0f32, 1f32)),
@ -257,6 +246,8 @@ impl WebGLRenderingContext {
current_clear_color: Cell::new((0.0, 0.0, 0.0, 0.0)),
extension_manager: WebGLExtensions::new(webgl_version),
capabilities: Default::default(),
default_vao: Default::default(),
current_vao: Default::default(),
}
})
}
@ -288,6 +279,14 @@ impl WebGLRenderingContext {
&self.limits
}
fn current_vao(&self) -> DomRoot<WebGLVertexArrayObjectOES> {
self.current_vao.or_init(|| {
DomRoot::from_ref(
self.default_vao.init_once(|| WebGLVertexArrayObjectOES::new(self, None)),
)
})
}
fn bound_texture(&self, target: u32) -> Option<DomRoot<WebGLTexture>> {
match target {
constants::TEXTURE_2D => {
@ -318,18 +317,6 @@ impl WebGLRenderingContext {
})
}
pub fn vertex_attribs(&self) -> &VertexAttribs {
&self.vertex_attribs
}
pub fn bound_buffer_element_array(&self) -> Option<DomRoot<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>) {
let (sender, receiver) = webgl_channel().unwrap();
self.webgl_sender.send_resize(size, sender).unwrap();
@ -382,10 +369,6 @@ impl WebGLRenderingContext {
self.webgl_sender.send_vr(command).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());
@ -1183,7 +1166,7 @@ impl WebGLRenderingContext {
handle_potential_webgl_error!(
self,
self.vertex_attribs.validate_for_draw(required_len, primcount as u32, &current_program.active_attribs()),
self.current_vao().validate_for_draw(required_len, primcount as u32, &current_program.active_attribs()),
return
);
@ -1234,7 +1217,7 @@ impl WebGLRenderingContext {
);
if count > 0 && primcount > 0 {
if let Some(array_buffer) = self.bound_buffer_element_array.get() {
if let Some(array_buffer) = self.current_vao().element_array_buffer().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 {
@ -1253,7 +1236,7 @@ impl WebGLRenderingContext {
// 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()),
self.current_vao().validate_for_draw(0, primcount as u32, &current_program.active_attribs()),
return
);
@ -1276,7 +1259,7 @@ impl WebGLRenderingContext {
return self.webgl_error(InvalidValue);
}
self.vertex_attribs.set_divisor(index, divisor);
self.current_vao().vertex_attrib_divisor(index, divisor);
self.send_command(WebGLCommand::VertexAttribDivisor { index, divisor });
}
@ -1312,14 +1295,66 @@ impl WebGLRenderingContext {
Some(receiver.recv().unwrap().into())
}
pub fn array_buffer(&self) -> Option<DomRoot<WebGLBuffer>> {
self.bound_buffer_array.get()
}
pub fn bound_buffer(&self, target: u32) -> WebGLResult<Option<DomRoot<WebGLBuffer>>> {
match target {
constants::ARRAY_BUFFER => Ok(self.bound_buffer_array.get()),
constants::ELEMENT_ARRAY_BUFFER => Ok(self.bound_buffer_element_array.get()),
constants::ELEMENT_ARRAY_BUFFER => Ok(self.current_vao().element_array_buffer().get()),
_ => Err(WebGLError::InvalidEnum),
}
}
pub fn create_vertex_array(&self) -> Option<DomRoot<WebGLVertexArrayObjectOES>> {
let (sender, receiver) = webgl_channel().unwrap();
self.send_command(WebGLCommand::CreateVertexArray(sender));
receiver.recv().unwrap().map(|id| WebGLVertexArrayObjectOES::new(self, Some(id)))
}
pub fn delete_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) {
if let Some(vao) = vao {
handle_potential_webgl_error!(self, self.validate_ownership(vao), return);
// The default vertex array has no id and should never be passed around.
assert!(vao.id().is_some());
if vao.is_deleted() {
return;
}
if vao == &*self.current_vao() {
// Setting it to None will make self.current_vao() reset it to the default one
// next time it is called.
self.current_vao.set(None);
self.send_command(WebGLCommand::BindVertexArray(None));
}
vao.delete();
}
}
pub fn is_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) -> bool {
vao.map_or(false, |vao| {
// The default vertex array has no id and should never be passed around.
assert!(vao.id().is_some());
self.validate_ownership(vao).is_ok() && vao.ever_bound() && !vao.is_deleted()
})
}
pub fn bind_vertex_array(&self, vao: Option<&WebGLVertexArrayObjectOES>) {
if let Some(vao) = vao {
// The default vertex array has no id and should never be passed around.
assert!(vao.id().is_some());
handle_potential_webgl_error!(self, self.validate_ownership(vao), return);
if vao.is_deleted() {
return self.webgl_error(InvalidOperation);
}
vao.set_ever_bound();
}
self.send_command(WebGLCommand::BindVertexArray(vao.and_then(|vao| vao.id())));
// Setting it to None will make self.current_vao() reset it to the default one
// next time it is called.
self.current_vao.set(vao);
}
fn validate_blend_mode(&self, mode: u32) -> WebGLResult<()> {
match mode {
constants::FUNC_ADD |
@ -1414,29 +1449,40 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
#[allow(unsafe_code)]
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
unsafe fn GetParameter(&self, cx: *mut JSContext, parameter: u32) -> JSVal {
// Handle the GL_*_BINDING without going all the way
// to the GL, since we would just need to map back from GL's
// returned ID to the WebGL* object we're tracking.
if !self.extension_manager.is_get_parameter_name_enabled(parameter) {
self.webgl_error(WebGLError::InvalidEnum);
return NullValue();
}
match parameter {
constants::ARRAY_BUFFER_BINDING =>
return object_binding_to_js_or_null!(cx, &self.bound_buffer_array),
constants::CURRENT_PROGRAM => {
return object_binding_to_js_or_null!(cx, &self.current_program);
constants::ARRAY_BUFFER_BINDING => {
return optional_root_object_to_js_or_null!(cx, &self.bound_buffer_array.get());
}
constants::CURRENT_PROGRAM => {
return optional_root_object_to_js_or_null!(cx, &self.current_program.get());
}
constants::ELEMENT_ARRAY_BUFFER_BINDING => {
let buffer = self.current_vao().element_array_buffer().get();
return optional_root_object_to_js_or_null!(cx, buffer);
}
constants::FRAMEBUFFER_BINDING => {
return optional_root_object_to_js_or_null!(cx, &self.bound_framebuffer.get());
}
constants::RENDERBUFFER_BINDING => {
return optional_root_object_to_js_or_null!(cx, &self.bound_renderbuffer.get());
}
constants::ELEMENT_ARRAY_BUFFER_BINDING =>
return object_binding_to_js_or_null!(cx, &self.bound_buffer_element_array),
constants::FRAMEBUFFER_BINDING =>
return object_binding_to_js_or_null!(cx, &self.bound_framebuffer),
constants::RENDERBUFFER_BINDING =>
return object_binding_to_js_or_null!(cx, &self.bound_renderbuffer),
constants::TEXTURE_BINDING_2D => {
let texture = self.bound_texture(constants::TEXTURE_2D);
return optional_root_object_to_js_or_null!(cx, texture)
},
return optional_root_object_to_js_or_null!(cx, texture);
}
constants::TEXTURE_BINDING_CUBE_MAP => {
let texture = self.bound_texture(constants::TEXTURE_CUBE_MAP);
return optional_root_object_to_js_or_null!(cx, texture)
},
return optional_root_object_to_js_or_null!(cx, texture);
}
OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES => {
let vao = self.current_vao.get().filter(|vao| vao.id().is_some());
return optional_root_object_to_js_or_null!(cx, vao);
}
// In readPixels we currently support RGBA/UBYTE only. If
// we wanted to support other formats, we could ask the
// driver, but we would need to check for
@ -1525,24 +1571,6 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return BooleanValue(value);
}
if !self.extension_manager.is_get_parameter_name_enabled(parameter) {
self.webgl_error(WebGLError::InvalidEnum);
return NullValue();
}
// 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();
}
}
}
match handle_potential_webgl_error!(self, Parameter::from_u32(parameter), return NullValue()) {
Parameter::Bool(param) => {
let (sender, receiver) = webgl_channel().unwrap();
@ -1793,23 +1821,30 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
handle_potential_webgl_error!(self, self.validate_ownership(buffer), return);
}
let current_vao;
let slot = match target {
constants::ARRAY_BUFFER => &self.bound_buffer_array,
constants::ELEMENT_ARRAY_BUFFER => &self.bound_buffer_element_array,
constants::ARRAY_BUFFER => {
&self.bound_buffer_array
}
constants::ELEMENT_ARRAY_BUFFER => {
current_vao = self.current_vao();
current_vao.element_array_buffer()
}
_ => return self.webgl_error(InvalidEnum),
};
if let Some(buffer) = buffer {
match buffer.bind(target) {
Ok(_) => slot.set(Some(buffer)),
Err(e) => return self.webgl_error(e),
if buffer.is_marked_for_deletion() {
return self.webgl_error(InvalidOperation);
}
} else {
slot.set(None);
// Unbind the current buffer
self.send_command(WebGLCommand::BindBuffer(target, None));
handle_potential_webgl_error!(self, buffer.set_target(target), return);
buffer.increment_attached_counter();
}
self.send_command(WebGLCommand::BindBuffer(target, buffer.map(|b| b.id())));
if let Some(old) = slot.get() {
old.decrement_attached_counter();
}
slot.set(buffer);
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
@ -2248,26 +2283,20 @@ 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 {
handle_potential_webgl_error!(self, self.validate_ownership(buffer), return);
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.
self.vertex_attribs.delete_buffer(buffer);
// 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,
Some(WebGLCommand::BindBuffer(constants::ELEMENT_ARRAY_BUFFER, None)));
buffer.delete()
let buffer = match buffer {
Some(buffer) => buffer,
None => return,
};
handle_potential_webgl_error!(self, self.validate_ownership(buffer), return);
if buffer.is_marked_for_deletion() {
return;
}
self.current_vao().unbind_buffer(buffer);
if self.bound_buffer_array.get().map_or(false, |b| buffer == &*b) {
self.bound_buffer_array.set(None);
buffer.decrement_attached_counter();
}
buffer.mark_for_deletion();
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
@ -2398,7 +2427,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
handle_potential_webgl_error!(
self,
self.vertex_attribs.validate_for_draw(required_len, 1, &current_program.active_attribs()),
self.current_vao().validate_for_draw(required_len, 1, &current_program.active_attribs()),
return
);
@ -2449,7 +2478,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
);
if count > 0 {
if let Some(array_buffer) = self.bound_buffer_element_array.get() {
if let Some(array_buffer) = self.current_vao().element_array_buffer().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 {
@ -2468,7 +2497,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, 1, &current_program.active_attribs()),
self.current_vao().validate_for_draw(0, 1, &current_program.active_attribs()),
return
);
@ -2486,7 +2515,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return self.webgl_error(InvalidValue);
}
self.vertex_attribs.enabled_as_array(attrib_id, true);
self.current_vao().enabled_vertex_attrib_array(attrib_id, true);
self.send_command(WebGLCommand::EnableVertexAttribArray(attrib_id));
}
@ -2496,7 +2525,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
return self.webgl_error(InvalidValue);
}
self.vertex_attribs.enabled_as_array(attrib_id, false);
self.current_vao().enabled_vertex_attrib_array(attrib_id, false);
self.send_command(WebGLCommand::DisableVertexAttribArray(attrib_id));
}
@ -2788,9 +2817,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
#[allow(unsafe_code)]
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
unsafe fn GetVertexAttrib(&self, cx: *mut JSContext, index: u32, param: u32) -> JSVal {
let current_vao = self.current_vao();
let data = handle_potential_webgl_error!(
self,
self.vertex_attribs.get(index).ok_or(InvalidValue),
current_vao.get_vertex_attrib(index).ok_or(InvalidValue),
return NullValue()
);
if param == constants::CURRENT_VERTEX_ATTRIB {
@ -2824,15 +2854,13 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
constants::VERTEX_ATTRIB_ARRAY_STRIDE => Int32Value(data.stride as i32),
constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING => {
rooted!(in(cx) let mut jsval = NullValue());
if let Some(data) = self.vertex_attribs.get(index) {
if let Some(buffer) = data.buffer() {
buffer.to_jsval(cx, jsval.handle_mut());
}
if let Some(buffer) = data.buffer() {
buffer.to_jsval(cx, jsval.handle_mut());
}
jsval.get()
}
ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE => {
Int32Value(data.divisor as i32)
UInt32Value(data.divisor)
}
_ => {
self.webgl_error(InvalidEnum);
@ -2847,9 +2875,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
self.webgl_error(InvalidEnum);
return 0;
}
let vao = self.current_vao();
let data = handle_potential_webgl_error!(
self,
self.vertex_attribs.get(index).ok_or(InvalidValue),
vao.get_vertex_attrib(index).ok_or(InvalidValue),
return 0
);
data.offset as i64
@ -2875,7 +2904,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
fn IsBuffer(&self, buffer: Option<&WebGLBuffer>) -> bool {
buffer.map_or(false, |buf| {
self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_deleted()
self.validate_ownership(buf).is_ok() && buf.target().is_some() && !buf.is_marked_for_deletion()
})
}
@ -3829,19 +3858,15 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
) {
handle_potential_webgl_error!(
self,
self.vertex_attribs.set_pointer(
self.current_vao().vertex_attrib_pointer(
index,
size,
type_,
normalized,
stride,
offset,
self.bound_buffer_array.get().as_ref().map(|buffer| &**buffer),
),
return
)
);
self.send_command(WebGLCommand::VertexAttribPointer(index, size, type_, normalized, stride, offset as u32));
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4
@ -4245,199 +4270,6 @@ impl LayoutCanvasWebGLRenderingContextHelpers for LayoutDom<WebGLRenderingContex
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[must_root]
pub struct VertexAttribs {
attribs: DomRefCell<Box<[VertexAttribData]>>,
}
impl VertexAttribs {
pub fn new(max: u32) -> Self {
// High-end GPUs have 16 of those, let's just use a boxed slice.
Self { attribs: DomRefCell::new(vec![Default::default(); max as usize].into()) }
}
pub fn clear(&self) {
for attrib in &mut **self.attribs.borrow_mut() {
*attrib = Default::default();
}
}
pub fn clone_from(&self, other: &Self) {
self.attribs.borrow_mut().clone_from_slice(&other.attribs.borrow());
}
pub fn set_pointer(
&self,
index: u32,
size: i32,
type_: u32,
normalized: bool,
stride: i32,
offset: i64,
buffer: Option<&WebGLBuffer>,
) -> WebGLResult<()> {
let mut attribs = self.attribs.borrow_mut();
let data = attribs.get_mut(index as usize).ok_or(InvalidValue)?;
if size < 1 || size > 4 {
return Err(InvalidValue);
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#BUFFER_OFFSET_AND_STRIDE
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#VERTEX_STRIDE
if stride < 0 || stride > 255 || offset < 0 {
return Err(InvalidValue);
}
let bytes_per_component: i32 = match type_ {
constants::BYTE | constants::UNSIGNED_BYTE => 1,
constants::SHORT | constants::UNSIGNED_SHORT => 2,
constants::FLOAT => 4,
_ => return Err(InvalidEnum),
};
if offset % bytes_per_component as i64 > 0 || stride % bytes_per_component > 0 {
return Err(InvalidOperation);
}
let buffer = buffer.ok_or(InvalidOperation)?;
*data = VertexAttribData {
enabled_as_array: data.enabled_as_array,
size: size as u8,
type_,
bytes_per_vertex: size as u8 * bytes_per_component as u8,
normalized,
stride: stride as u8,
offset: offset as u32,
buffer: Some(Dom::from_ref(buffer)),
divisor: data.divisor,
};
Ok(())
}
pub fn borrow(&self) -> Ref<[VertexAttribData]> {
Ref::map(self.attribs.borrow(), |attribs| &**attribs)
}
fn delete_buffer(&self, buffer: &WebGLBuffer) {
for attrib in &mut **self.attribs.borrow_mut() {
if attrib.buffer().map_or(false, |b| b.id() == buffer.id()) {
attrib.buffer = None;
}
}
}
fn get(&self, index: u32) -> Option<Ref<VertexAttribData>> {
ref_filter_map(self.attribs.borrow(), |attribs| attribs.get(index as usize))
}
fn enabled_as_array(&self, index: u32, value: bool) {
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);
}
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;
}
// 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(())
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[must_root]
pub struct VertexAttribData {
enabled_as_array: bool,
size: u8,
type_: u32,
bytes_per_vertex: u8,
normalized: bool,
stride: u8,
offset: u32,
buffer: Option<Dom<WebGLBuffer>>,
divisor: u32,
}
impl Default for VertexAttribData {
#[allow(unrooted_must_root)]
fn default() -> Self {
Self {
enabled_as_array: false,
size: 4,
type_: constants::FLOAT,
bytes_per_vertex: 16,
normalized: false,
stride: 0,
offset: 0,
buffer: None,
divisor: 0,
}
}
}
impl VertexAttribData {
pub fn buffer(&self) -> Option<&WebGLBuffer> {
self.buffer.as_ref().map(|b| &**b)
}
fn max_vertices(&self) -> u32 {
let capacity = (self.buffer().unwrap().capacity() as u32).saturating_sub(self.offset);
if capacity < self.bytes_per_vertex as u32 {
0
} else if self.stride == 0 {
capacity / self.bytes_per_vertex as u32
} else if self.stride < self.bytes_per_vertex {
(capacity - (self.bytes_per_vertex - self.stride) as u32) / self.stride as u32
} else {
let mut max = capacity / self.stride as u32;
if capacity % self.stride as u32 >= self.bytes_per_vertex as u32 {
max += 1;
}
max
}
}
}
#[derive(Default, JSTraceable, MallocSizeOf)]
struct Capabilities {
value: Cell<CapFlags>,

View file

@ -2,39 +2,44 @@
* 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::WebGLVertexArrayId;
use canvas_traits::webgl::{ActiveAttribInfo, WebGLCommand, WebGLError, WebGLResult, WebGLVertexArrayId};
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::codegen::Bindings::WebGLVertexArrayObjectOESBinding;
use dom::bindings::inheritance::Castable;
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::root::{DomRoot, MutNullableDom};
use dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use dom::webglbuffer::WebGLBuffer;
use dom::webglobject::WebGLObject;
use dom::webglrenderingcontext::{VertexAttribs, WebGLRenderingContext};
use dom::webglrenderingcontext::WebGLRenderingContext;
use dom_struct::dom_struct;
use std::cell::Cell;
use ref_filter_map::ref_filter_map;
use std::cell::{Cell, Ref};
#[dom_struct]
pub struct WebGLVertexArrayObjectOES {
webgl_object_: WebGLObject,
id: WebGLVertexArrayId,
id: Option<WebGLVertexArrayId>,
ever_bound: Cell<bool>,
is_deleted: Cell<bool>,
vertex_attribs: VertexAttribs,
bound_buffer_element_array: MutNullableDom<WebGLBuffer>,
vertex_attribs: DomRefCell<Box<[VertexAttribData]>>,
element_array_buffer: MutNullableDom<WebGLBuffer>,
}
impl WebGLVertexArrayObjectOES {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLVertexArrayId) -> Self {
fn new_inherited(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> Self {
let max_vertex_attribs = context.limits().max_vertex_attribs as usize;
Self {
webgl_object_: WebGLObject::new_inherited(context),
id: id,
ever_bound: Cell::new(false),
is_deleted: Cell::new(false),
vertex_attribs: VertexAttribs::new(context.limits().max_vertex_attribs),
bound_buffer_element_array: MutNullableDom::new(None),
id,
ever_bound: Default::default(),
is_deleted: Default::default(),
vertex_attribs: DomRefCell::new(vec![Default::default(); max_vertex_attribs].into()),
element_array_buffer: Default::default(),
}
}
pub fn new(context: &WebGLRenderingContext, id: WebGLVertexArrayId) -> DomRoot<Self> {
pub fn new(context: &WebGLRenderingContext, id: Option<WebGLVertexArrayId>) -> DomRoot<Self> {
reflect_dom_object(
Box::new(WebGLVertexArrayObjectOES::new_inherited(context, id)),
&*context.global(),
@ -42,11 +47,7 @@ impl WebGLVertexArrayObjectOES {
)
}
pub fn vertex_attribs(&self) -> &VertexAttribs {
&self.vertex_attribs
}
pub fn id(&self) -> WebGLVertexArrayId {
pub fn id(&self) -> Option<WebGLVertexArrayId> {
self.id
}
@ -54,8 +55,25 @@ impl WebGLVertexArrayObjectOES {
self.is_deleted.get()
}
pub fn set_deleted(&self) {
self.is_deleted.set(true)
pub fn delete(&self) {
assert!(self.id.is_some());
if self.is_deleted.get() {
return;
}
self.is_deleted.set(true);
self.upcast::<WebGLObject>()
.context()
.send_command(WebGLCommand::DeleteVertexArray(self.id.unwrap()));
for attrib_data in &**self.vertex_attribs.borrow() {
if let Some(buffer) = attrib_data.buffer() {
buffer.decrement_attached_counter();
}
}
if let Some(buffer) = self.element_array_buffer.get() {
buffer.decrement_attached_counter();
}
}
pub fn ever_bound(&self) -> bool {
@ -66,11 +84,202 @@ impl WebGLVertexArrayObjectOES {
self.ever_bound.set(true);
}
pub fn bound_buffer_element_array(&self) -> Option<DomRoot<WebGLBuffer>> {
self.bound_buffer_element_array.get()
pub fn element_array_buffer(&self) -> &MutNullableDom<WebGLBuffer> {
&self.element_array_buffer
}
pub fn set_bound_buffer_element_array(&self, buffer: Option<&WebGLBuffer>) {
self.bound_buffer_element_array.set(buffer);
pub fn get_vertex_attrib(&self, index: u32) -> Option<Ref<VertexAttribData>> {
ref_filter_map(self.vertex_attribs.borrow(), |attribs| attribs.get(index as usize))
}
pub fn vertex_attrib_pointer(
&self,
index: u32,
size: i32,
type_: u32,
normalized: bool,
stride: i32,
offset: i64,
) -> WebGLResult<()> {
let mut attribs = self.vertex_attribs.borrow_mut();
let data = attribs.get_mut(index as usize).ok_or(WebGLError::InvalidValue)?;
if size < 1 || size > 4 {
return Err(WebGLError::InvalidValue);
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#BUFFER_OFFSET_AND_STRIDE
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#VERTEX_STRIDE
if stride < 0 || stride > 255 || offset < 0 {
return Err(WebGLError::InvalidValue);
}
let bytes_per_component: i32 = match type_ {
constants::BYTE | constants::UNSIGNED_BYTE => 1,
constants::SHORT | constants::UNSIGNED_SHORT => 2,
constants::FLOAT => 4,
_ => return Err(WebGLError::InvalidEnum),
};
if offset % bytes_per_component as i64 > 0 || stride % bytes_per_component > 0 {
return Err(WebGLError::InvalidOperation);
}
let context = self.upcast::<WebGLObject>().context();
let buffer = context.array_buffer().ok_or(WebGLError::InvalidOperation)?;
buffer.increment_attached_counter();
context.send_command(WebGLCommand::VertexAttribPointer(
index,
size,
type_,
normalized,
stride,
offset as u32,
));
if let Some(old) = data.buffer() {
old.decrement_attached_counter();
}
*data = VertexAttribData {
enabled_as_array: data.enabled_as_array,
size: size as u8,
type_,
bytes_per_vertex: size as u8 * bytes_per_component as u8,
normalized,
stride: stride as u8,
offset: offset as u32,
buffer: Some(Dom::from_ref(&*buffer)),
divisor: data.divisor,
};
Ok(())
}
pub fn vertex_attrib_divisor(&self, index: u32, value: u32) {
self.vertex_attribs.borrow_mut()[index as usize].divisor = value;
}
pub fn enabled_vertex_attrib_array(&self, index: u32, value: bool) {
self.vertex_attribs.borrow_mut()[index as usize].enabled_as_array = value;
}
pub fn unbind_buffer(&self, buffer: &WebGLBuffer) {
for attrib in &mut **self.vertex_attribs.borrow_mut() {
if let Some(b) = attrib.buffer() {
if b.id() != buffer.id() {
continue;
}
b.decrement_attached_counter();
}
attrib.buffer = None;
}
if self.element_array_buffer.get().map_or(false, |b| buffer == &*b) {
buffer.decrement_attached_counter();
self.element_array_buffer.set(None);
}
}
pub fn validate_for_draw(
&self,
required_len: u32,
instance_count: u32,
active_attribs: &[ActiveAttribInfo],
) -> WebGLResult<()> {
// TODO(nox): Cache limits per VAO.
let attribs = self.vertex_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(WebGLError::InvalidOperation);
}
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;
}
// 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(WebGLError::InvalidOperation);
}
} else if max_vertices.checked_mul(attrib.divisor).map_or(false, |v| v < instance_count) {
return Err(WebGLError::InvalidOperation);
}
}
}
if has_active_attrib && !has_divisor_0 {
return Err(WebGLError::InvalidOperation);
}
Ok(())
}
}
impl Drop for WebGLVertexArrayObjectOES {
fn drop(&mut self) {
if self.id.is_some() {
self.delete();
}
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[must_root]
pub struct VertexAttribData {
pub enabled_as_array: bool,
pub size: u8,
pub type_: u32,
bytes_per_vertex: u8,
pub normalized: bool,
pub stride: u8,
pub offset: u32,
pub buffer: Option<Dom<WebGLBuffer>>,
pub divisor: u32,
}
impl Default for VertexAttribData {
#[allow(unrooted_must_root)]
fn default() -> Self {
Self {
enabled_as_array: false,
size: 4,
type_: constants::FLOAT,
bytes_per_vertex: 16,
normalized: false,
stride: 0,
offset: 0,
buffer: None,
divisor: 0,
}
}
}
impl VertexAttribData {
pub fn buffer(&self) -> Option<&WebGLBuffer> {
self.buffer.as_ref().map(|b| &**b)
}
fn max_vertices(&self) -> u32 {
let capacity = (self.buffer().unwrap().capacity() as u32).saturating_sub(self.offset);
if capacity < self.bytes_per_vertex as u32 {
0
} else if self.stride == 0 {
capacity / self.bytes_per_vertex as u32
} else if self.stride < self.bytes_per_vertex {
(capacity - (self.bytes_per_vertex - self.stride) as u32) / self.stride as u32
} else {
let mut max = capacity / self.stride as u32;
if capacity % self.stride as u32 >= self.bytes_per_vertex as u32 {
max += 1;
}
max
}
}
}