cesium
Version:
Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
457 lines (376 loc) • 17.5 kB
JavaScript
/*global define*/
define([
'../Core/ComponentDatatype',
'../Core/defaultValue',
'../Core/defined',
'../Core/destroyObject',
'../Core/DeveloperError',
'../Core/Math',
'./Buffer',
'./BufferUsage',
'./VertexArray'
], function(
ComponentDatatype,
defaultValue,
defined,
destroyObject,
DeveloperError,
CesiumMath,
Buffer,
BufferUsage,
VertexArray) {
'use strict';
/**
* @private
*/
function VertexArrayFacade(context, attributes, sizeInVertices, instanced) {
//>>includeStart('debug', pragmas.debug);
if (!context) {
throw new DeveloperError('context is required.');
}
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);
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.');
}
}
// 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;
if (uniqueIndices[index]) {
throw new DeveloperError('Index ' + index + ' is used by more than one attribute.');
}
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 numberOfVertexArrays = defined(indexBuffer) ? Math.ceil(this._size / (CesiumMath.SIXTY_FOUR_KILOBYTES - 1)) : 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 * (CesiumMath.SIXTY_FOUR_KILOBYTES - 1));
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)) ? (CesiumMath.SIXTY_FOUR_KILOBYTES - 1) : (this._size % (CesiumMath.SIXTY_FOUR_KILOBYTES - 1)))
// 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);
};
return VertexArrayFacade;
});