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