| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 | 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 CesiumMath from "../Core/Math.js";import Buffer from "./Buffer.js";import BufferUsage from "./BufferUsage.js";import VertexArray from "./VertexArray.js";/** * @private */function VertexArrayFacade(context, attributes, sizeInVertices, instanced) {  //>>includeStart('debug', pragmas.debug);  Check.defined("context", context);  if (!attributes || attributes.length === 0) {    throw new DeveloperError("At least one attribute is required.");  }  //>>includeEnd('debug');  const attrs = VertexArrayFacade._verifyAttributes(attributes);  sizeInVertices = defaultValue(sizeInVertices, 0);  const precreatedAttributes = [];  const attributesByUsage = {};  let attributesForUsage;  let usage;  // Bucket the attributes by usage.  const length = attrs.length;  for (let i = 0; i < length; ++i) {    const attribute = attrs[i];    // If the attribute already has a vertex buffer, we do not need    // to manage a vertex buffer or typed array for it.    if (attribute.vertexBuffer) {      precreatedAttributes.push(attribute);      continue;    }    usage = attribute.usage;    attributesForUsage = attributesByUsage[usage];    if (!defined(attributesForUsage)) {      attributesForUsage = attributesByUsage[usage] = [];    }    attributesForUsage.push(attribute);  }  // A function to sort attributes by the size of their components.  From left to right, a vertex  // stores floats, shorts, and then bytes.  function compare(left, right) {    return (      ComponentDatatype.getSizeInBytes(right.componentDatatype) -      ComponentDatatype.getSizeInBytes(left.componentDatatype)    );  }  this._allBuffers = [];  for (usage in attributesByUsage) {    if (attributesByUsage.hasOwnProperty(usage)) {      attributesForUsage = attributesByUsage[usage];      attributesForUsage.sort(compare);      const vertexSizeInBytes = VertexArrayFacade._vertexSizeInBytes(        attributesForUsage      );      const bufferUsage = attributesForUsage[0].usage;      const buffer = {        vertexSizeInBytes: vertexSizeInBytes,        vertexBuffer: undefined,        usage: bufferUsage,        needsCommit: false,        arrayBuffer: undefined,        arrayViews: VertexArrayFacade._createArrayViews(          attributesForUsage,          vertexSizeInBytes        ),      };      this._allBuffers.push(buffer);    }  }  this._size = 0;  this._instanced = defaultValue(instanced, false);  this._precreated = precreatedAttributes;  this._context = context;  this.writers = undefined;  this.va = undefined;  this.resize(sizeInVertices);}VertexArrayFacade._verifyAttributes = function (attributes) {  const attrs = [];  for (let i = 0; i < attributes.length; ++i) {    const attribute = attributes[i];    const attr = {      index: defaultValue(attribute.index, i),      enabled: defaultValue(attribute.enabled, true),      componentsPerAttribute: attribute.componentsPerAttribute,      componentDatatype: defaultValue(        attribute.componentDatatype,        ComponentDatatype.FLOAT      ),      normalize: defaultValue(attribute.normalize, false),      // There will be either a vertexBuffer or an [optional] usage.      vertexBuffer: attribute.vertexBuffer,      usage: defaultValue(attribute.usage, BufferUsage.STATIC_DRAW),    };    attrs.push(attr);    //>>includeStart('debug', pragmas.debug);    if (      attr.componentsPerAttribute !== 1 &&      attr.componentsPerAttribute !== 2 &&      attr.componentsPerAttribute !== 3 &&      attr.componentsPerAttribute !== 4    ) {      throw new DeveloperError(        "attribute.componentsPerAttribute must be in the range [1, 4]."      );    }    const datatype = attr.componentDatatype;    if (!ComponentDatatype.validate(datatype)) {      throw new DeveloperError(        "Attribute must have a valid componentDatatype or not specify it."      );    }    if (!BufferUsage.validate(attr.usage)) {      throw new DeveloperError(        "Attribute must have a valid usage or not specify it."      );    }    //>>includeEnd('debug');  }  // Verify all attribute names are unique.  const uniqueIndices = new Array(attrs.length);  for (let j = 0; j < attrs.length; ++j) {    const currentAttr = attrs[j];    const index = currentAttr.index;    //>>includeStart('debug', pragmas.debug);    if (uniqueIndices[index]) {      throw new DeveloperError(        `Index ${index} is used by more than one attribute.`      );    }    //>>includeEnd('debug');    uniqueIndices[index] = true;  }  return attrs;};VertexArrayFacade._vertexSizeInBytes = function (attributes) {  let sizeInBytes = 0;  const length = attributes.length;  for (let i = 0; i < length; ++i) {    const attribute = attributes[i];    sizeInBytes +=      attribute.componentsPerAttribute *      ComponentDatatype.getSizeInBytes(attribute.componentDatatype);  }  const maxComponentSizeInBytes =    length > 0      ? ComponentDatatype.getSizeInBytes(attributes[0].componentDatatype)      : 0; // Sorted by size  const remainder =    maxComponentSizeInBytes > 0 ? sizeInBytes % maxComponentSizeInBytes : 0;  const padding = remainder === 0 ? 0 : maxComponentSizeInBytes - remainder;  sizeInBytes += padding;  return sizeInBytes;};VertexArrayFacade._createArrayViews = function (attributes, vertexSizeInBytes) {  const views = [];  let offsetInBytes = 0;  const length = attributes.length;  for (let i = 0; i < length; ++i) {    const attribute = attributes[i];    const componentDatatype = attribute.componentDatatype;    views.push({      index: attribute.index,      enabled: attribute.enabled,      componentsPerAttribute: attribute.componentsPerAttribute,      componentDatatype: componentDatatype,      normalize: attribute.normalize,      offsetInBytes: offsetInBytes,      vertexSizeInComponentType:        vertexSizeInBytes / ComponentDatatype.getSizeInBytes(componentDatatype),      view: undefined,    });    offsetInBytes +=      attribute.componentsPerAttribute *      ComponentDatatype.getSizeInBytes(componentDatatype);  }  return views;};/** * Invalidates writers.  Can't render again until commit is called. */VertexArrayFacade.prototype.resize = function (sizeInVertices) {  this._size = sizeInVertices;  const allBuffers = this._allBuffers;  this.writers = [];  for (let i = 0, len = allBuffers.length; i < len; ++i) {    const buffer = allBuffers[i];    VertexArrayFacade._resize(buffer, this._size);    // Reserving invalidates the writers, so if client's cache them, they need to invalidate their cache.    VertexArrayFacade._appendWriters(this.writers, buffer);  }  // VAs are recreated next time commit is called.  destroyVA(this);};VertexArrayFacade._resize = function (buffer, size) {  if (buffer.vertexSizeInBytes > 0) {    // Create larger array buffer    const arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes);    // Copy contents from previous array buffer    if (defined(buffer.arrayBuffer)) {      const destView = new Uint8Array(arrayBuffer);      const sourceView = new Uint8Array(buffer.arrayBuffer);      const sourceLength = sourceView.length;      for (let j = 0; j < sourceLength; ++j) {        destView[j] = sourceView[j];      }    }    // Create typed views into the new array buffer    const views = buffer.arrayViews;    const length = views.length;    for (let i = 0; i < length; ++i) {      const view = views[i];      view.view = ComponentDatatype.createArrayBufferView(        view.componentDatatype,        arrayBuffer,        view.offsetInBytes      );    }    buffer.arrayBuffer = arrayBuffer;  }};const createWriters = [  // 1 component per attribute  function (buffer, view, vertexSizeInComponentType) {    return function (index, attribute) {      view[index * vertexSizeInComponentType] = attribute;      buffer.needsCommit = true;    };  },  // 2 component per attribute  function (buffer, view, vertexSizeInComponentType) {    return function (index, component0, component1) {      const i = index * vertexSizeInComponentType;      view[i] = component0;      view[i + 1] = component1;      buffer.needsCommit = true;    };  },  // 3 component per attribute  function (buffer, view, vertexSizeInComponentType) {    return function (index, component0, component1, component2) {      const i = index * vertexSizeInComponentType;      view[i] = component0;      view[i + 1] = component1;      view[i + 2] = component2;      buffer.needsCommit = true;    };  },  // 4 component per attribute  function (buffer, view, vertexSizeInComponentType) {    return function (index, component0, component1, component2, component3) {      const i = index * vertexSizeInComponentType;      view[i] = component0;      view[i + 1] = component1;      view[i + 2] = component2;      view[i + 3] = component3;      buffer.needsCommit = true;    };  },];VertexArrayFacade._appendWriters = function (writers, buffer) {  const arrayViews = buffer.arrayViews;  const length = arrayViews.length;  for (let i = 0; i < length; ++i) {    const arrayView = arrayViews[i];    writers[arrayView.index] = createWriters[      arrayView.componentsPerAttribute - 1    ](buffer, arrayView.view, arrayView.vertexSizeInComponentType);  }};VertexArrayFacade.prototype.commit = function (indexBuffer) {  let recreateVA = false;  const allBuffers = this._allBuffers;  let buffer;  let i;  let length;  for (i = 0, length = allBuffers.length; i < length; ++i) {    buffer = allBuffers[i];    recreateVA = commit(this, buffer) || recreateVA;  }  ///////////////////////////////////////////////////////////////////////  if (recreateVA || !defined(this.va)) {    destroyVA(this);    const va = (this.va = []);    const chunkSize = CesiumMath.SIXTY_FOUR_KILOBYTES - 4; // The 65535 index is reserved for primitive restart. Reserve the last 4 indices so that billboard quads are not broken up.    const numberOfVertexArrays =      defined(indexBuffer) && !this._instanced        ? Math.ceil(this._size / chunkSize)        : 1;    for (let k = 0; k < numberOfVertexArrays; ++k) {      let attributes = [];      for (i = 0, length = allBuffers.length; i < length; ++i) {        buffer = allBuffers[i];        const offset = k * (buffer.vertexSizeInBytes * chunkSize);        VertexArrayFacade._appendAttributes(          attributes,          buffer,          offset,          this._instanced        );      }      attributes = attributes.concat(this._precreated);      va.push({        va: new VertexArray({          context: this._context,          attributes: attributes,          indexBuffer: indexBuffer,        }),        indicesCount:          1.5 *          (k !== numberOfVertexArrays - 1 ? chunkSize : this._size % chunkSize),        // TODO: not hardcode 1.5, this assumes 6 indices per 4 vertices (as for Billboard quads).      });    }  }};function commit(vertexArrayFacade, buffer) {  if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {    buffer.needsCommit = false;    const vertexBuffer = buffer.vertexBuffer;    const vertexBufferSizeInBytes =      vertexArrayFacade._size * buffer.vertexSizeInBytes;    const vertexBufferDefined = defined(vertexBuffer);    if (      !vertexBufferDefined ||      vertexBuffer.sizeInBytes < vertexBufferSizeInBytes    ) {      if (vertexBufferDefined) {        vertexBuffer.destroy();      }      buffer.vertexBuffer = Buffer.createVertexBuffer({        context: vertexArrayFacade._context,        typedArray: buffer.arrayBuffer,        usage: buffer.usage,      });      buffer.vertexBuffer.vertexArrayDestroyable = false;      return true; // Created new vertex buffer    }    buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer);  }  return false; // Did not create new vertex buffer}VertexArrayFacade._appendAttributes = function (  attributes,  buffer,  vertexBufferOffset,  instanced) {  const arrayViews = buffer.arrayViews;  const length = arrayViews.length;  for (let i = 0; i < length; ++i) {    const view = arrayViews[i];    attributes.push({      index: view.index,      enabled: view.enabled,      componentsPerAttribute: view.componentsPerAttribute,      componentDatatype: view.componentDatatype,      normalize: view.normalize,      vertexBuffer: buffer.vertexBuffer,      offsetInBytes: vertexBufferOffset + view.offsetInBytes,      strideInBytes: buffer.vertexSizeInBytes,      instanceDivisor: instanced ? 1 : 0,    });  }};VertexArrayFacade.prototype.subCommit = function (  offsetInVertices,  lengthInVertices) {  //>>includeStart('debug', pragmas.debug);  if (offsetInVertices < 0 || offsetInVertices >= this._size) {    throw new DeveloperError(      "offsetInVertices must be greater than or equal to zero and less than the vertex array size."    );  }  if (offsetInVertices + lengthInVertices > this._size) {    throw new DeveloperError(      "offsetInVertices + lengthInVertices cannot exceed the vertex array size."    );  }  //>>includeEnd('debug');  const allBuffers = this._allBuffers;  for (let i = 0, len = allBuffers.length; i < len; ++i) {    subCommit(allBuffers[i], offsetInVertices, lengthInVertices);  }};function subCommit(buffer, offsetInVertices, lengthInVertices) {  if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {    const byteOffset = buffer.vertexSizeInBytes * offsetInVertices;    const byteLength = buffer.vertexSizeInBytes * lengthInVertices;    // PERFORMANCE_IDEA: If we want to get really crazy, we could consider updating    // individual attributes instead of the entire (sub-)vertex.    //    // PERFORMANCE_IDEA: Does creating the typed view add too much GC overhead?    buffer.vertexBuffer.copyFromArrayView(      new Uint8Array(buffer.arrayBuffer, byteOffset, byteLength),      byteOffset    );  }}VertexArrayFacade.prototype.endSubCommits = function () {  const allBuffers = this._allBuffers;  for (let i = 0, len = allBuffers.length; i < len; ++i) {    allBuffers[i].needsCommit = false;  }};function destroyVA(vertexArrayFacade) {  const va = vertexArrayFacade.va;  if (!defined(va)) {    return;  }  const length = va.length;  for (let i = 0; i < length; ++i) {    va[i].va.destroy();  }  vertexArrayFacade.va = undefined;}VertexArrayFacade.prototype.isDestroyed = function () {  return false;};VertexArrayFacade.prototype.destroy = function () {  const allBuffers = this._allBuffers;  for (let i = 0, len = allBuffers.length; i < len; ++i) {    const buffer = allBuffers[i];    buffer.vertexBuffer = buffer.vertexBuffer && buffer.vertexBuffer.destroy();  }  destroyVA(this);  return destroyObject(this);};export default VertexArrayFacade;
 |