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