| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 | import Check from "../Core/Check.js";import ComponentDatatype from "../Core/ComponentDatatype.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import DeveloperError from "../Core/DeveloperError.js";import Geometry from "../Core/Geometry.js";import IndexDatatype from "../Core/IndexDatatype.js";import CesiumMath from "../Core/Math.js";import RuntimeError from "../Core/RuntimeError.js";import Buffer from "./Buffer.js";import BufferUsage from "./BufferUsage.js";import ContextLimits from "./ContextLimits.js";function addAttribute(attributes, attribute, index, context) {  const hasVertexBuffer = defined(attribute.vertexBuffer);  const hasValue = defined(attribute.value);  const componentsPerAttribute = attribute.value    ? attribute.value.length    : attribute.componentsPerAttribute;  //>>includeStart('debug', pragmas.debug);  if (!hasVertexBuffer && !hasValue) {    throw new DeveloperError("attribute must have a vertexBuffer or a value.");  }  if (hasVertexBuffer && hasValue) {    throw new DeveloperError(      "attribute cannot have both a vertexBuffer and a value.  It must have either a vertexBuffer property defining per-vertex data or a value property defining data for all vertices."    );  }  if (    componentsPerAttribute !== 1 &&    componentsPerAttribute !== 2 &&    componentsPerAttribute !== 3 &&    componentsPerAttribute !== 4  ) {    if (hasValue) {      throw new DeveloperError(        "attribute.value.length must be in the range [1, 4]."      );    }    throw new DeveloperError(      "attribute.componentsPerAttribute must be in the range [1, 4]."    );  }  if (    defined(attribute.componentDatatype) &&    !ComponentDatatype.validate(attribute.componentDatatype)  ) {    throw new DeveloperError(      "attribute must have a valid componentDatatype or not specify it."    );  }  if (defined(attribute.strideInBytes) && attribute.strideInBytes > 255) {    // WebGL limit.  Not in GL ES.    throw new DeveloperError(      "attribute must have a strideInBytes less than or equal to 255 or not specify it."    );  }  if (    defined(attribute.instanceDivisor) &&    attribute.instanceDivisor > 0 &&    !context.instancedArrays  ) {    throw new DeveloperError("instanced arrays is not supported");  }  if (defined(attribute.instanceDivisor) && attribute.instanceDivisor < 0) {    throw new DeveloperError(      "attribute must have an instanceDivisor greater than or equal to zero"    );  }  if (defined(attribute.instanceDivisor) && hasValue) {    throw new DeveloperError(      "attribute cannot have have an instanceDivisor if it is not backed by a buffer"    );  }  if (    defined(attribute.instanceDivisor) &&    attribute.instanceDivisor > 0 &&    attribute.index === 0  ) {    throw new DeveloperError(      "attribute zero cannot have an instanceDivisor greater than 0"    );  }  //>>includeEnd('debug');  // Shallow copy the attribute; we do not want to copy the vertex buffer.  const attr = {    index: defaultValue(attribute.index, index),    enabled: defaultValue(attribute.enabled, true),    vertexBuffer: attribute.vertexBuffer,    value: hasValue ? attribute.value.slice(0) : undefined,    componentsPerAttribute: componentsPerAttribute,    componentDatatype: defaultValue(      attribute.componentDatatype,      ComponentDatatype.FLOAT    ),    normalize: defaultValue(attribute.normalize, false),    offsetInBytes: defaultValue(attribute.offsetInBytes, 0),    strideInBytes: defaultValue(attribute.strideInBytes, 0),    instanceDivisor: defaultValue(attribute.instanceDivisor, 0),  };  if (hasVertexBuffer) {    // Common case: vertex buffer for per-vertex data    attr.vertexAttrib = function (gl) {      const index = this.index;      gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer._getBuffer());      gl.vertexAttribPointer(        index,        this.componentsPerAttribute,        this.componentDatatype,        this.normalize,        this.strideInBytes,        this.offsetInBytes      );      gl.enableVertexAttribArray(index);      if (this.instanceDivisor > 0) {        context.glVertexAttribDivisor(index, this.instanceDivisor);        context._vertexAttribDivisors[index] = this.instanceDivisor;        context._previousDrawInstanced = true;      }    };    attr.disableVertexAttribArray = function (gl) {      gl.disableVertexAttribArray(this.index);      if (this.instanceDivisor > 0) {        context.glVertexAttribDivisor(index, 0);      }    };  } else {    // Less common case: value array for the same data for each vertex    switch (attr.componentsPerAttribute) {      case 1:        attr.vertexAttrib = function (gl) {          gl.vertexAttrib1fv(this.index, this.value);        };        break;      case 2:        attr.vertexAttrib = function (gl) {          gl.vertexAttrib2fv(this.index, this.value);        };        break;      case 3:        attr.vertexAttrib = function (gl) {          gl.vertexAttrib3fv(this.index, this.value);        };        break;      case 4:        attr.vertexAttrib = function (gl) {          gl.vertexAttrib4fv(this.index, this.value);        };        break;    }    attr.disableVertexAttribArray = function (gl) {};  }  attributes.push(attr);}function bind(gl, attributes, indexBuffer) {  for (let i = 0; i < attributes.length; ++i) {    const attribute = attributes[i];    if (attribute.enabled) {      attribute.vertexAttrib(gl);    }  }  if (defined(indexBuffer)) {    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._getBuffer());  }}/** * Creates a vertex array, which defines the attributes making up a vertex, and contains an optional index buffer * to select vertices for rendering.  Attributes are defined using object literals as shown in Example 1 below. * * @param {Object} options Object with the following properties: * @param {Context} options.context The context in which the VertexArray gets created. * @param {Object[]} options.attributes An array of attributes. * @param {IndexBuffer} [options.indexBuffer] An optional index buffer. * * @returns {VertexArray} The vertex array, ready for use with drawing. * * @exception {DeveloperError} Attribute must have a <code>vertexBuffer</code>. * @exception {DeveloperError} Attribute must have a <code>componentsPerAttribute</code>. * @exception {DeveloperError} Attribute must have a valid <code>componentDatatype</code> or not specify it. * @exception {DeveloperError} Attribute must have a <code>strideInBytes</code> less than or equal to 255 or not specify it. * @exception {DeveloperError} Index n is used by more than one attribute. * * * @example * // Example 1. Create a vertex array with vertices made up of three floating point * // values, e.g., a position, from a single vertex buffer.  No index buffer is used. * const positionBuffer = Buffer.createVertexBuffer({ *     context : context, *     sizeInBytes : 12, *     usage : BufferUsage.STATIC_DRAW * }); * const attributes = [ *     { *         index                  : 0, *         enabled                : true, *         vertexBuffer           : positionBuffer, *         componentsPerAttribute : 3, *         componentDatatype      : ComponentDatatype.FLOAT, *         normalize              : false, *         offsetInBytes          : 0, *         strideInBytes          : 0 // tightly packed *         instanceDivisor        : 0 // not instanced *     } * ]; * const va = new VertexArray({ *     context : context, *     attributes : attributes * }); * * @example * // Example 2. Create a vertex array with vertices from two different vertex buffers. * // Each vertex has a three-component position and three-component normal. * const positionBuffer = Buffer.createVertexBuffer({ *     context : context, *     sizeInBytes : 12, *     usage : BufferUsage.STATIC_DRAW * }); * const normalBuffer = Buffer.createVertexBuffer({ *     context : context, *     sizeInBytes : 12, *     usage : BufferUsage.STATIC_DRAW * }); * const attributes = [ *     { *         index                  : 0, *         vertexBuffer           : positionBuffer, *         componentsPerAttribute : 3, *         componentDatatype      : ComponentDatatype.FLOAT *     }, *     { *         index                  : 1, *         vertexBuffer           : normalBuffer, *         componentsPerAttribute : 3, *         componentDatatype      : ComponentDatatype.FLOAT *     } * ]; * const va = new VertexArray({ *     context : context, *     attributes : attributes * }); * * @example * // Example 3. Creates the same vertex layout as Example 2 using a single * // vertex buffer, instead of two. * const buffer = Buffer.createVertexBuffer({ *     context : context, *     sizeInBytes : 24, *     usage : BufferUsage.STATIC_DRAW * }); * const attributes = [ *     { *         vertexBuffer           : buffer, *         componentsPerAttribute : 3, *         componentDatatype      : ComponentDatatype.FLOAT, *         offsetInBytes          : 0, *         strideInBytes          : 24 *     }, *     { *         vertexBuffer           : buffer, *         componentsPerAttribute : 3, *         componentDatatype      : ComponentDatatype.FLOAT, *         normalize              : true, *         offsetInBytes          : 12, *         strideInBytes          : 24 *     } * ]; * const va = new VertexArray({ *     context : context, *     attributes : attributes * }); * * @see Buffer#createVertexBuffer * @see Buffer#createIndexBuffer * @see Context#draw * * @private */function VertexArray(options) {  options = defaultValue(options, defaultValue.EMPTY_OBJECT);  //>>includeStart('debug', pragmas.debug);  Check.defined("options.context", options.context);  Check.defined("options.attributes", options.attributes);  //>>includeEnd('debug');  const context = options.context;  const gl = context._gl;  const attributes = options.attributes;  const indexBuffer = options.indexBuffer;  let i;  const vaAttributes = [];  let numberOfVertices = 1; // if every attribute is backed by a single value  let hasInstancedAttributes = false;  let hasConstantAttributes = false;  let length = attributes.length;  for (i = 0; i < length; ++i) {    addAttribute(vaAttributes, attributes[i], i, context);  }  length = vaAttributes.length;  for (i = 0; i < length; ++i) {    const attribute = vaAttributes[i];    if (defined(attribute.vertexBuffer) && attribute.instanceDivisor === 0) {      // This assumes that each vertex buffer in the vertex array has the same number of vertices.      const bytes =        attribute.strideInBytes ||        attribute.componentsPerAttribute *          ComponentDatatype.getSizeInBytes(attribute.componentDatatype);      numberOfVertices = attribute.vertexBuffer.sizeInBytes / bytes;      break;    }  }  for (i = 0; i < length; ++i) {    if (vaAttributes[i].instanceDivisor > 0) {      hasInstancedAttributes = true;    }    if (defined(vaAttributes[i].value)) {      hasConstantAttributes = true;    }  }  //>>includeStart('debug', pragmas.debug);  // Verify all attribute names are unique  const uniqueIndices = {};  for (i = 0; i < length; ++i) {    const index = vaAttributes[i].index;    if (uniqueIndices[index]) {      throw new DeveloperError(        `Index ${index} is used by more than one attribute.`      );    }    uniqueIndices[index] = true;  }  //>>includeEnd('debug');  let vao;  // Setup VAO if supported  if (context.vertexArrayObject) {    vao = context.glCreateVertexArray();    context.glBindVertexArray(vao);    bind(gl, vaAttributes, indexBuffer);    context.glBindVertexArray(null);  }  this._numberOfVertices = numberOfVertices;  this._hasInstancedAttributes = hasInstancedAttributes;  this._hasConstantAttributes = hasConstantAttributes;  this._context = context;  this._gl = gl;  this._vao = vao;  this._attributes = vaAttributes;  this._indexBuffer = indexBuffer;}function computeNumberOfVertices(attribute) {  return attribute.values.length / attribute.componentsPerAttribute;}function computeAttributeSizeInBytes(attribute) {  return (    ComponentDatatype.getSizeInBytes(attribute.componentDatatype) *    attribute.componentsPerAttribute  );}function interleaveAttributes(attributes) {  let j;  let name;  let attribute;  // Extract attribute names.  const names = [];  for (name in attributes) {    // Attribute needs to have per-vertex values; not a constant value for all vertices.    if (      attributes.hasOwnProperty(name) &&      defined(attributes[name]) &&      defined(attributes[name].values)    ) {      names.push(name);      if (attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {        attributes[name].componentDatatype = ComponentDatatype.FLOAT;        attributes[name].values = ComponentDatatype.createTypedArray(          ComponentDatatype.FLOAT,          attributes[name].values        );      }    }  }  // Validation.  Compute number of vertices.  let numberOfVertices;  const namesLength = names.length;  if (namesLength > 0) {    numberOfVertices = computeNumberOfVertices(attributes[names[0]]);    for (j = 1; j < namesLength; ++j) {      const currentNumberOfVertices = computeNumberOfVertices(        attributes[names[j]]      );      if (currentNumberOfVertices !== numberOfVertices) {        throw new RuntimeError(          `${            "Each attribute list must have the same number of vertices.  " +            "Attribute "          }${names[j]} has a different number of vertices ` +            `(${currentNumberOfVertices.toString()})` +            ` than attribute ${names[0]} (${numberOfVertices.toString()}).`        );      }    }  }  // Sort attributes by the size of their components.  From left to right, a vertex stores floats, shorts, and then bytes.  names.sort(function (left, right) {    return (      ComponentDatatype.getSizeInBytes(attributes[right].componentDatatype) -      ComponentDatatype.getSizeInBytes(attributes[left].componentDatatype)    );  });  // Compute sizes and strides.  let vertexSizeInBytes = 0;  const offsetsInBytes = {};  for (j = 0; j < namesLength; ++j) {    name = names[j];    attribute = attributes[name];    offsetsInBytes[name] = vertexSizeInBytes;    vertexSizeInBytes += computeAttributeSizeInBytes(attribute);  }  if (vertexSizeInBytes > 0) {    // Pad each vertex to be a multiple of the largest component datatype so each    // attribute can be addressed using typed arrays.    const maxComponentSizeInBytes = ComponentDatatype.getSizeInBytes(      attributes[names[0]].componentDatatype    ); // Sorted large to small    const remainder = vertexSizeInBytes % maxComponentSizeInBytes;    if (remainder !== 0) {      vertexSizeInBytes += maxComponentSizeInBytes - remainder;    }    // Total vertex buffer size in bytes, including per-vertex padding.    const vertexBufferSizeInBytes = numberOfVertices * vertexSizeInBytes;    // Create array for interleaved vertices.  Each attribute has a different view (pointer) into the array.    const buffer = new ArrayBuffer(vertexBufferSizeInBytes);    const views = {};    for (j = 0; j < namesLength; ++j) {      name = names[j];      const sizeInBytes = ComponentDatatype.getSizeInBytes(        attributes[name].componentDatatype      );      views[name] = {        pointer: ComponentDatatype.createTypedArray(          attributes[name].componentDatatype,          buffer        ),        index: offsetsInBytes[name] / sizeInBytes, // Offset in ComponentType        strideInComponentType: vertexSizeInBytes / sizeInBytes,      };    }    // Copy attributes into one interleaved array.    // PERFORMANCE_IDEA:  Can we optimize these loops?    for (j = 0; j < numberOfVertices; ++j) {      for (let n = 0; n < namesLength; ++n) {        name = names[n];        attribute = attributes[name];        const values = attribute.values;        const view = views[name];        const pointer = view.pointer;        const numberOfComponents = attribute.componentsPerAttribute;        for (let k = 0; k < numberOfComponents; ++k) {          pointer[view.index + k] = values[j * numberOfComponents + k];        }        view.index += view.strideInComponentType;      }    }    return {      buffer: buffer,      offsetsInBytes: offsetsInBytes,      vertexSizeInBytes: vertexSizeInBytes,    };  }  // No attributes to interleave.  return undefined;}/** * Creates a vertex array from a geometry.  A geometry contains vertex attributes and optional index data * in system memory, whereas a vertex array contains vertex buffers and an optional index buffer in WebGL * memory for use with rendering. * <br /><br /> * The <code>geometry</code> argument should use the standard layout like the geometry returned by {@link BoxGeometry}. * <br /><br /> * <code>options</code> can have four properties: * <ul> *   <li><code>geometry</code>:  The source geometry containing data used to create the vertex array.</li> *   <li><code>attributeLocations</code>:  An object that maps geometry attribute names to vertex shader attribute locations.</li> *   <li><code>bufferUsage</code>:  The expected usage pattern of the vertex array's buffers.  On some WebGL implementations, this can significantly affect performance.  See {@link BufferUsage}.  Default: <code>BufferUsage.DYNAMIC_DRAW</code>.</li> *   <li><code>interleave</code>:  Determines if all attributes are interleaved in a single vertex buffer or if each attribute is stored in a separate vertex buffer.  Default: <code>false</code>.</li> * </ul> * <br /> * If <code>options</code> is not specified or the <code>geometry</code> contains no data, the returned vertex array is empty. * * @param {Object} options An object defining the geometry, attribute indices, buffer usage, and vertex layout used to create the vertex array. * * @exception {RuntimeError} Each attribute list must have the same number of vertices. * @exception {DeveloperError} The geometry must have zero or one index lists. * @exception {DeveloperError} Index n is used by more than one attribute. * * * @example * // Example 1. Creates a vertex array for rendering a box.  The default dynamic draw * // usage is used for the created vertex and index buffer.  The attributes are not * // interleaved by default. * const geometry = new BoxGeometry(); * const va = VertexArray.fromGeometry({ *     context            : context, *     geometry           : geometry, *     attributeLocations : GeometryPipeline.createAttributeLocations(geometry), * }); * * @example * // Example 2. Creates a vertex array with interleaved attributes in a * // single vertex buffer.  The vertex and index buffer have static draw usage. * const va = VertexArray.fromGeometry({ *     context            : context, *     geometry           : geometry, *     attributeLocations : GeometryPipeline.createAttributeLocations(geometry), *     bufferUsage        : BufferUsage.STATIC_DRAW, *     interleave         : true * }); * * @example * // Example 3.  When the caller destroys the vertex array, it also destroys the * // attached vertex buffer(s) and index buffer. * va = va.destroy(); * * @see Buffer#createVertexBuffer * @see Buffer#createIndexBuffer * @see GeometryPipeline.createAttributeLocations * @see ShaderProgram */VertexArray.fromGeometry = function (options) {  options = defaultValue(options, defaultValue.EMPTY_OBJECT);  //>>includeStart('debug', pragmas.debug);  Check.defined("options.context", options.context);  //>>includeEnd('debug');  const context = options.context;  const geometry = defaultValue(options.geometry, defaultValue.EMPTY_OBJECT);  const bufferUsage = defaultValue(    options.bufferUsage,    BufferUsage.DYNAMIC_DRAW  );  const attributeLocations = defaultValue(    options.attributeLocations,    defaultValue.EMPTY_OBJECT  );  const interleave = defaultValue(options.interleave, false);  const createdVAAttributes = options.vertexArrayAttributes;  let name;  let attribute;  let vertexBuffer;  const vaAttributes = defined(createdVAAttributes) ? createdVAAttributes : [];  const attributes = geometry.attributes;  if (interleave) {    // Use a single vertex buffer with interleaved vertices.    const interleavedAttributes = interleaveAttributes(attributes);    if (defined(interleavedAttributes)) {      vertexBuffer = Buffer.createVertexBuffer({        context: context,        typedArray: interleavedAttributes.buffer,        usage: bufferUsage,      });      const offsetsInBytes = interleavedAttributes.offsetsInBytes;      const strideInBytes = interleavedAttributes.vertexSizeInBytes;      for (name in attributes) {        if (attributes.hasOwnProperty(name) && defined(attributes[name])) {          attribute = attributes[name];          if (defined(attribute.values)) {            // Common case: per-vertex attributes            vaAttributes.push({              index: attributeLocations[name],              vertexBuffer: vertexBuffer,              componentDatatype: attribute.componentDatatype,              componentsPerAttribute: attribute.componentsPerAttribute,              normalize: attribute.normalize,              offsetInBytes: offsetsInBytes[name],              strideInBytes: strideInBytes,            });          } else {            // Constant attribute for all vertices            vaAttributes.push({              index: attributeLocations[name],              value: attribute.value,              componentDatatype: attribute.componentDatatype,              normalize: attribute.normalize,            });          }        }      }    }  } else {    // One vertex buffer per attribute.    for (name in attributes) {      if (attributes.hasOwnProperty(name) && defined(attributes[name])) {        attribute = attributes[name];        let componentDatatype = attribute.componentDatatype;        if (componentDatatype === ComponentDatatype.DOUBLE) {          componentDatatype = ComponentDatatype.FLOAT;        }        vertexBuffer = undefined;        if (defined(attribute.values)) {          vertexBuffer = Buffer.createVertexBuffer({            context: context,            typedArray: ComponentDatatype.createTypedArray(              componentDatatype,              attribute.values            ),            usage: bufferUsage,          });        }        vaAttributes.push({          index: attributeLocations[name],          vertexBuffer: vertexBuffer,          value: attribute.value,          componentDatatype: componentDatatype,          componentsPerAttribute: attribute.componentsPerAttribute,          normalize: attribute.normalize,        });      }    }  }  let indexBuffer;  const indices = geometry.indices;  if (defined(indices)) {    if (      Geometry.computeNumberOfVertices(geometry) >=        CesiumMath.SIXTY_FOUR_KILOBYTES &&      context.elementIndexUint    ) {      indexBuffer = Buffer.createIndexBuffer({        context: context,        typedArray: new Uint32Array(indices),        usage: bufferUsage,        indexDatatype: IndexDatatype.UNSIGNED_INT,      });    } else {      indexBuffer = Buffer.createIndexBuffer({        context: context,        typedArray: new Uint16Array(indices),        usage: bufferUsage,        indexDatatype: IndexDatatype.UNSIGNED_SHORT,      });    }  }  return new VertexArray({    context: context,    attributes: vaAttributes,    indexBuffer: indexBuffer,  });};Object.defineProperties(VertexArray.prototype, {  numberOfAttributes: {    get: function () {      return this._attributes.length;    },  },  numberOfVertices: {    get: function () {      return this._numberOfVertices;    },  },  indexBuffer: {    get: function () {      return this._indexBuffer;    },  },});/** * index is the location in the array of attributes, not the index property of an attribute. */VertexArray.prototype.getAttribute = function (index) {  //>>includeStart('debug', pragmas.debug);  Check.defined("index", index);  //>>includeEnd('debug');  return this._attributes[index];};// Workaround for ANGLE, where the attribute divisor seems to be part of the global state instead// of the VAO state. This function is called when the vao is bound, and should be removed// once the ANGLE issue is resolved. Setting the divisor should normally happen in vertexAttrib and// disableVertexAttribArray.function setVertexAttribDivisor(vertexArray) {  const context = vertexArray._context;  const hasInstancedAttributes = vertexArray._hasInstancedAttributes;  if (!hasInstancedAttributes && !context._previousDrawInstanced) {    return;  }  context._previousDrawInstanced = hasInstancedAttributes;  const divisors = context._vertexAttribDivisors;  const attributes = vertexArray._attributes;  const maxAttributes = ContextLimits.maximumVertexAttributes;  let i;  if (hasInstancedAttributes) {    const length = attributes.length;    for (i = 0; i < length; ++i) {      const attribute = attributes[i];      if (attribute.enabled) {        const divisor = attribute.instanceDivisor;        const index = attribute.index;        if (divisor !== divisors[index]) {          context.glVertexAttribDivisor(index, divisor);          divisors[index] = divisor;        }      }    }  } else {    for (i = 0; i < maxAttributes; ++i) {      if (divisors[i] > 0) {        context.glVertexAttribDivisor(i, 0);        divisors[i] = 0;      }    }  }}// Vertex attributes backed by a constant value go through vertexAttrib[1234]f[v]// which is part of context state rather than VAO state.function setConstantAttributes(vertexArray, gl) {  const attributes = vertexArray._attributes;  const length = attributes.length;  for (let i = 0; i < length; ++i) {    const attribute = attributes[i];    if (attribute.enabled && defined(attribute.value)) {      attribute.vertexAttrib(gl);    }  }}VertexArray.prototype._bind = function () {  if (defined(this._vao)) {    this._context.glBindVertexArray(this._vao);    if (this._context.instancedArrays) {      setVertexAttribDivisor(this);    }    if (this._hasConstantAttributes) {      setConstantAttributes(this, this._gl);    }  } else {    bind(this._gl, this._attributes, this._indexBuffer);  }};VertexArray.prototype._unBind = function () {  if (defined(this._vao)) {    this._context.glBindVertexArray(null);  } else {    const attributes = this._attributes;    const gl = this._gl;    for (let i = 0; i < attributes.length; ++i) {      const attribute = attributes[i];      if (attribute.enabled) {        attribute.disableVertexAttribArray(gl);      }    }    if (this._indexBuffer) {      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);    }  }};VertexArray.prototype.isDestroyed = function () {  return false;};VertexArray.prototype.destroy = function () {  const attributes = this._attributes;  for (let i = 0; i < attributes.length; ++i) {    const vertexBuffer = attributes[i].vertexBuffer;    if (      defined(vertexBuffer) &&      !vertexBuffer.isDestroyed() &&      vertexBuffer.vertexArrayDestroyable    ) {      vertexBuffer.destroy();    }  }  const indexBuffer = this._indexBuffer;  if (    defined(indexBuffer) &&    !indexBuffer.isDestroyed() &&    indexBuffer.vertexArrayDestroyable  ) {    indexBuffer.destroy();  }  if (defined(this._vao)) {    this._context.glDeleteVertexArray(this._vao);  }  return destroyObject(this);};export default VertexArray;
 |