You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

515 lines
15 KiB
JavaScript

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');
var attrs = VertexArrayFacade._verifyAttributes(attributes);
sizeInVertices = defaultValue(sizeInVertices, 0);
var precreatedAttributes = [];
var attributesByUsage = {};
var attributesForUsage;
var usage;
// Bucket the attributes by usage.
var length = attrs.length;
for (var i = 0; i < length; ++i) {
var 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);
var vertexSizeInBytes = VertexArrayFacade._vertexSizeInBytes(
attributesForUsage
);
var bufferUsage = attributesForUsage[0].usage;
var 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) {
var attrs = [];
for (var i = 0; i < attributes.length; ++i) {
var attribute = attributes[i];
var 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]."
);
}
var 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.
var uniqueIndices = new Array(attrs.length);
for (var j = 0; j < attrs.length; ++j) {
var currentAttr = attrs[j];
var 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) {
var sizeInBytes = 0;
var length = attributes.length;
for (var i = 0; i < length; ++i) {
var attribute = attributes[i];
sizeInBytes +=
attribute.componentsPerAttribute *
ComponentDatatype.getSizeInBytes(attribute.componentDatatype);
}
var maxComponentSizeInBytes =
length > 0
? ComponentDatatype.getSizeInBytes(attributes[0].componentDatatype)
: 0; // Sorted by size
var remainder =
maxComponentSizeInBytes > 0 ? sizeInBytes % maxComponentSizeInBytes : 0;
var padding = remainder === 0 ? 0 : maxComponentSizeInBytes - remainder;
sizeInBytes += padding;
return sizeInBytes;
};
VertexArrayFacade._createArrayViews = function (attributes, vertexSizeInBytes) {
var views = [];
var offsetInBytes = 0;
var length = attributes.length;
for (var i = 0; i < length; ++i) {
var attribute = attributes[i];
var 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;
var allBuffers = this._allBuffers;
this.writers = [];
for (var i = 0, len = allBuffers.length; i < len; ++i) {
var 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
var arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes);
// Copy contents from previous array buffer
if (defined(buffer.arrayBuffer)) {
var destView = new Uint8Array(arrayBuffer);
var sourceView = new Uint8Array(buffer.arrayBuffer);
var sourceLength = sourceView.length;
for (var j = 0; j < sourceLength; ++j) {
destView[j] = sourceView[j];
}
}
// Create typed views into the new array buffer
var views = buffer.arrayViews;
var length = views.length;
for (var i = 0; i < length; ++i) {
var view = views[i];
view.view = ComponentDatatype.createArrayBufferView(
view.componentDatatype,
arrayBuffer,
view.offsetInBytes
);
}
buffer.arrayBuffer = arrayBuffer;
}
};
var 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) {
var 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) {
var 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) {
var 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) {
var arrayViews = buffer.arrayViews;
var length = arrayViews.length;
for (var i = 0; i < length; ++i) {
var arrayView = arrayViews[i];
writers[arrayView.index] = createWriters[
arrayView.componentsPerAttribute - 1
](buffer, arrayView.view, arrayView.vertexSizeInComponentType);
}
};
VertexArrayFacade.prototype.commit = function (indexBuffer) {
var recreateVA = false;
var allBuffers = this._allBuffers;
var buffer;
var i;
var 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);
var va = (this.va = []);
var 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.
var numberOfVertexArrays =
defined(indexBuffer) && !this._instanced
? Math.ceil(this._size / chunkSize)
: 1;
for (var k = 0; k < numberOfVertexArrays; ++k) {
var attributes = [];
for (i = 0, length = allBuffers.length; i < length; ++i) {
buffer = allBuffers[i];
var 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;
var vertexBuffer = buffer.vertexBuffer;
var vertexBufferSizeInBytes =
vertexArrayFacade._size * buffer.vertexSizeInBytes;
var 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
) {
var arrayViews = buffer.arrayViews;
var length = arrayViews.length;
for (var i = 0; i < length; ++i) {
var 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');
var allBuffers = this._allBuffers;
for (var i = 0, len = allBuffers.length; i < len; ++i) {
subCommit(allBuffers[i], offsetInVertices, lengthInVertices);
}
};
function subCommit(buffer, offsetInVertices, lengthInVertices) {
if (buffer.needsCommit && buffer.vertexSizeInBytes > 0) {
var byteOffset = buffer.vertexSizeInBytes * offsetInVertices;
var 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 () {
var allBuffers = this._allBuffers;
for (var i = 0, len = allBuffers.length; i < len; ++i) {
allBuffers[i].needsCommit = false;
}
};
function destroyVA(vertexArrayFacade) {
var va = vertexArrayFacade.va;
if (!defined(va)) {
return;
}
var length = va.length;
for (var i = 0; i < length; ++i) {
va[i].va.destroy();
}
vertexArrayFacade.va = undefined;
}
VertexArrayFacade.prototype.isDestroyed = function () {
return false;
};
VertexArrayFacade.prototype.destroy = function () {
var allBuffers = this._allBuffers;
for (var i = 0, len = allBuffers.length; i < len; ++i) {
var buffer = allBuffers[i];
buffer.vertexBuffer = buffer.vertexBuffer && buffer.vertexBuffer.destroy();
}
destroyVA(this);
return destroyObject(this);
};
export default VertexArrayFacade;