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;
|