diff --git a/Source/Core/Geometry.js b/Source/Core/Geometry.js index 55dd903af05c..c10078c250ef 100644 --- a/Source/Core/Geometry.js +++ b/Source/Core/Geometry.js @@ -158,7 +158,7 @@ define([ /** * @private */ - this.boundingSphereCV = undefined; + this.boundingSphereCV = options.boundingSphereCV; } /** diff --git a/Source/Core/GeometryPipeline.js b/Source/Core/GeometryPipeline.js index ad9079d35a72..95b1027baef9 100644 --- a/Source/Core/GeometryPipeline.js +++ b/Source/Core/GeometryPipeline.js @@ -1033,7 +1033,7 @@ define([ var instance = instances[i]; if (defined(instance.geometry)) { instanceGeometry.push(instance); - } else { + } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) { instanceSplitGeometry.push(instance); } } diff --git a/Source/Scene/DebugAppearance.js b/Source/Scene/DebugAppearance.js index 5de436b25ac4..70047a7fd72a 100644 --- a/Source/Scene/DebugAppearance.js +++ b/Source/Scene/DebugAppearance.js @@ -26,6 +26,7 @@ define([ * * @param {Object} options Object with the following properties: * @param {String} options.attributeName The name of the attribute to visualize. + * @param {Boolean} options.perInstanceAttribute Boolean that determines whether this attribute is a per-instance geometry attribute. * @param {String} [options.glslDatatype='vec3'] The GLSL datatype of the attribute. Supported datatypes are float, vec2, vec3, and vec4. * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader. * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader. @@ -44,11 +45,15 @@ define([ function DebugAppearance(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var attributeName = options.attributeName; + var perInstanceAttribute = options.perInstanceAttribute; //>>includeStart('debug', pragmas.debug); if (!defined(attributeName)) { throw new DeveloperError('options.attributeName is required.'); } + if (!defined(perInstanceAttribute)) { + throw new DeveloperError('options.perInstanceAttribute is required.'); + } //>>includeEnd('debug'); var glslDatatype = defaultValue(options.glslDatatype, 'vec3'); @@ -85,12 +90,15 @@ define([ var vs = 'attribute vec3 position3DHigh;\n' + 'attribute vec3 position3DLow;\n' + - 'attribute ' + glslDatatype + ' ' + attributeName + ';\n' + + 'attribute float batchId;\n' + + (perInstanceAttribute ? '' : 'attribute ' + glslDatatype + ' ' + attributeName + ';\n') + 'varying ' + glslDatatype + ' ' + varyingName + ';\n' + 'void main()\n' + '{\n' + 'vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n' + - varyingName + ' = ' + attributeName + ';\n' + + (perInstanceAttribute ? + varyingName + ' = czm_batchTable_' + attributeName + '(batchId);\n' : + varyingName + ' = ' + attributeName + ';\n') + 'gl_Position = czm_modelViewProjectionRelativeToEye * p;\n' + '}'; var fs = diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index b60a7d8213df..99cd3548d4e2 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -740,8 +740,10 @@ define([ var context = frameState.context; var vs = ShadowVolumeVS; + vs = primitive._primitive._batchTable.getVertexShaderCallback()(vs); vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); vs = Primitive._appendShowToShader(primitive._primitive, vs); + vs = Primitive._updateColorAttribute(primitive._primitive, vs); var fs = ShadowVolumeFS; var attributeLocations = primitive._primitive._attributeLocations; @@ -755,6 +757,9 @@ define([ }); if (primitive._primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + var pickFS = new ShaderSource({ sources : [fs], pickColorQualifier : 'varying' @@ -762,7 +767,7 @@ define([ primitive._spPick = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._spPick, - vertexShaderSource : ShaderSource.createPickVertexShaderSource(vs), + vertexShaderSource : vsPick, fragmentShaderSource : pickFS, attributeLocations : attributeLocations }); @@ -782,6 +787,7 @@ define([ colorCommands.length = length; var vaIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); for (var i = 0; i < length; i += 3) { var vertexArray = primitive._va[vaIndex++]; @@ -798,7 +804,7 @@ define([ command.vertexArray = vertexArray; command.renderState = groundPrimitive._rsStencilPreloadPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // stencil depth command @@ -813,7 +819,7 @@ define([ command.vertexArray = vertexArray; command.renderState = groundPrimitive._rsStencilDepthPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // color command @@ -828,7 +834,7 @@ define([ command.vertexArray = vertexArray; command.renderState = groundPrimitive._rsColorPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; } } @@ -840,6 +846,7 @@ define([ pickCommands.length = length; var pickIndex = 0; + var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap); for (var j = 0; j < length; j += 3) { var pickOffset = pickOffsets[pickIndex++]; @@ -862,7 +869,7 @@ define([ command.count = count; command.renderState = groundPrimitive._rsStencilPreloadPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // stencil depth command @@ -879,7 +886,7 @@ define([ command.count = count; command.renderState = groundPrimitive._rsStencilDepthPass; command.shaderProgram = groundPrimitive._sp; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; // color command @@ -896,7 +903,7 @@ define([ command.count = count; command.renderState = groundPrimitive._rsPickPass; command.shaderProgram = groundPrimitive._spPick; - command.uniformMap = groundPrimitive._uniformMap; + command.uniformMap = uniformMap; command.pass = Pass.GROUND; } } diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index 10869066594e..42dc138af54b 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -18,8 +18,10 @@ define([ '../Core/Math', '../Core/Matrix4', '../Core/Plane', + '../Core/RuntimeError', '../Renderer/Buffer', '../Renderer/BufferUsage', + '../Renderer/ContextLimits', '../Renderer/DrawCommand', '../Renderer/RenderState', '../Renderer/ShaderProgram', @@ -53,8 +55,10 @@ define([ CesiumMath, Matrix4, Plane, + RuntimeError, Buffer, BufferUsage, + ContextLimits, DrawCommand, RenderState, ShaderProgram, @@ -392,7 +396,14 @@ define([ } /** - * @private + * Called when {@link Viewer} or {@link CesiumWidget} render the scene to + * get the draw commands needed to render this primitive. + *

+ * Do not call this function directly. This is documented just to + * list the exceptions that may be propagated when the scene is rendered: + *

+ * + * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero. */ PolylineCollection.prototype.update = function(frameState) { removePolylines(this); @@ -409,6 +420,9 @@ define([ var properties = this._propertiesChanged; if (this._createBatchTable) { + if (ContextLimits.maximumVertexTextureImageUnits === 0) { + throw new RuntimeError('Vertex texture fetch support is required to render polylines. The maximum number of vertex texture image units must be greater than zero.'); + } createBatchTable(this, context); this._createBatchTable = false; } diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index b939397d79c0..cbdf724d6e3a 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -3,7 +3,9 @@ define([ '../Core/BoundingSphere', '../Core/Cartesian2', '../Core/Cartesian3', + '../Core/Cartesian4', '../Core/clone', + '../Core/Color', '../Core/combine', '../Core/ComponentDatatype', '../Core/defaultValue', @@ -19,16 +21,19 @@ define([ '../Core/GeometryInstanceAttribute', '../Core/isArray', '../Core/Matrix4', + '../Core/RuntimeError', '../Core/subdivideArray', '../Core/TaskProcessor', '../Renderer/Buffer', '../Renderer/BufferUsage', + '../Renderer/ContextLimits', '../Renderer/DrawCommand', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', '../Renderer/VertexArray', '../ThirdParty/when', + './BatchTable', './CullFace', './Pass', './PrimitivePipeline', @@ -39,7 +44,9 @@ define([ BoundingSphere, Cartesian2, Cartesian3, + Cartesian4, clone, + Color, combine, ComponentDatatype, defaultValue, @@ -55,16 +62,19 @@ define([ GeometryInstanceAttribute, isArray, Matrix4, + RuntimeError, subdivideArray, TaskProcessor, Buffer, BufferUsage, + ContextLimits, DrawCommand, RenderState, ShaderProgram, ShaderSource, VertexArray, when, + BatchTable, CullFace, Pass, PrimitivePipeline, @@ -297,21 +307,17 @@ define([ this._state = PrimitiveState.READY; this._geometries = []; - this._vaAttributes = undefined; this._error = undefined; this._numberOfInstances = 0; - this._validModelMatrix = false; this._boundingSpheres = []; this._boundingSphereWC = []; this._boundingSphereCV = []; this._boundingSphere2D = []; this._boundingSphereMorph = []; - this._perInstanceAttributeLocations = undefined; this._perInstanceAttributeCache = []; this._instanceIds = []; this._lastPerInstanceAttributeIndex = 0; - this._dirtyAttributes = []; this._va = []; this._attributeLocations = undefined; @@ -342,6 +348,11 @@ define([ this._createGeometryResults = undefined; this._ready = false; this._readyPromise = when.defer(); + + this._batchTable = undefined; + this._batchTableAttributeIndices = undefined; + this._instanceBoundingSpheres = undefined; + this._instanceBoundingSpheresCV = undefined; } defineProperties(Primitive.prototype, { @@ -470,6 +481,144 @@ define([ } }); + function getCommonPerInstanceAttributeNames(instances) { + var length = instances.length; + + var attributesInAllInstances = []; + var attributes0 = instances[0].attributes; + var name; + + for (name in attributes0) { + if (attributes0.hasOwnProperty(name)) { + var attribute = attributes0[name]; + var inAllInstances = true; + + // Does this same attribute exist in all instances? + for (var i = 1; i < length; ++i) { + var otherAttribute = instances[i].attributes[name]; + + if (!defined(otherAttribute) || + (attribute.componentDatatype !== otherAttribute.componentDatatype) || + (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || + (attribute.normalize !== otherAttribute.normalize)) { + + inAllInstances = false; + break; + } + } + + if (inAllInstances) { + attributesInAllInstances.push(name); + } + } + } + + return attributesInAllInstances; + } + + var scratchGetAttributeCartesian2 = new Cartesian2(); + var scratchGetAttributeCartesian3 = new Cartesian3(); + var scratchGetAttributeCartesian4 = new Cartesian4(); + + function getAttributeValue(value) { + var componentsPerAttribute = value.length; + if (componentsPerAttribute === 1) { + return value[0]; + } else if (componentsPerAttribute === 2) { + return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2); + } else if (componentsPerAttribute === 3) { + return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3); + } else if (componentsPerAttribute === 4) { + return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4); + } + } + + function createBatchTable(primitive, context) { + var geometryInstances = primitive.geometryInstances; + var instances = (isArray(geometryInstances)) ? geometryInstances : [geometryInstances]; + var numberOfInstances = instances.length; + if (numberOfInstances === 0) { + return; + } + + var names = getCommonPerInstanceAttributeNames(instances); + var length = names.length; + + var allowPicking = primitive.allowPicking; + var attributesLength = allowPicking ? length + 1 : length; + var attributes = new Array(attributesLength); + var attributeIndices = {}; + + var firstInstance = instances[0]; + var instanceAttributes = firstInstance.attributes; + + var i; + var name; + var attribute; + + for (i = 0; i < length; ++i) { + name = names[i]; + attribute = instanceAttributes[name]; + + attributeIndices[name] = i; + attributes[i] = { + functionName : 'czm_batchTable_' + name, + componentDatatype : attribute.componentDatatype, + componentsPerAttribute : attribute.componentsPerAttribute, + normalize : attribute.normalize + }; + } + + if (allowPicking) { + attributes[attributesLength - 1] = { + functionName : 'czm_batchTable_pickColor', + componentDatatype : ComponentDatatype.UNSIGNED_BYTE, + componentsPerAttribute : 4, + normalize : true + }; + } + + var batchTable = new BatchTable(attributes, numberOfInstances); + + for (i = 0; i < numberOfInstances; ++i) { + var instance = instances[i]; + instanceAttributes = instance.attributes; + + for (var j = 0; j < length; ++j) { + name = names[j]; + attribute = instanceAttributes[name]; + var value = getAttributeValue(attribute.value); + var attributeIndex = attributeIndices[name]; + batchTable.setBatchedAttribute(i, attributeIndex, value); + } + + if (allowPicking) { + var pickObject = { + primitive : defaultValue(instance.pickPrimitive, primitive) + }; + + if (defined(instance.id)) { + pickObject.id = instance.id; + } + + var pickId = context.createPickId(pickObject); + primitive._pickIds.push(pickId); + + var pickColor = pickId.color; + var color = scratchGetAttributeCartesian4; + color.x = Color.floatToByte(pickColor.red); + color.y = Color.floatToByte(pickColor.green); + color.z = Color.floatToByte(pickColor.blue); + color.w = Color.floatToByte(pickColor.alpha); + + batchTable.setBatchedAttribute(i, attributesLength - 1, color); + } + } + + primitive._batchTable = batchTable; + primitive._batchTableAttributeIndices = attributeIndices; + } + function cloneAttribute(attribute) { var clonedValues; if (isArray(attribute.values)) { @@ -508,37 +657,13 @@ define([ }); } - function cloneGeometryInstanceAttribute(attribute) { - var clonedValue; - if (isArray(attribute.value)) { - clonedValue = attribute.value.slice(0); - } else { - clonedValue = new attribute.value.constructor(attribute.value); - } - return new GeometryInstanceAttribute({ - componentDatatype : attribute.componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - normalize : attribute.normalize, - value : clonedValue - }); - } - function cloneInstance(instance, geometry) { - var attributes = instance.attributes; - var newAttributes = {}; - for (var property in attributes) { - if (attributes.hasOwnProperty(property)) { - newAttributes[property] = cloneGeometryInstanceAttribute(attributes[property]); - } - } - - return new GeometryInstance({ + return { geometry : geometry, modelMatrix : Matrix4.clone(instance.modelMatrix), - attributes : newAttributes, pickPrimitive : instance.pickPrimitive, id : instance.id - }); + }; } var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g; @@ -619,22 +744,44 @@ define([ }; Primitive._appendShowToShader = function(primitive, vertexShaderSource) { - if (!defined(primitive._attributeLocations.show)) { + if (!defined(primitive._batchTableAttributeIndices.show)) { return vertexShaderSource; } var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_show_main'); var showMain = - 'attribute float show;\n' + 'void main() \n' + '{ \n' + ' czm_non_show_main(); \n' + - ' gl_Position *= show; \n' + + ' gl_Position *= czm_batchTable_show(batchId); \n' + '}'; return renamedVS + '\n' + showMain; }; + Primitive._updateColorAttribute = function(primitive, vertexShaderSource) { + // some appearances have a color attribute for per vertex color. + // only remove if color is a per instance attribute. + if (!defined(primitive._batchTableAttributeIndices.color)) { + return vertexShaderSource; + } + + if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) { + return vertexShaderSource; + } + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, ''); + modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2'); + return modifiedVS; + }; + + Primitive._updatePickColorAttribute = function(source) { + var vsPick = source.replace(/attribute\s+vec4\s+pickColor;/g, ''); + vsPick = vsPick.replace(/(\b)pickColor(\b)/g, '$1czm_batchTable_pickColor(batchId)$2'); + return vsPick; + }; + function modifyForEncodedNormals(primitive, vertexShaderSource) { if (!primitive.compressVertices) { return vertexShaderSource; @@ -729,27 +876,6 @@ define([ //>>includeEnd('debug'); } - function createPickIds(context, primitive, instances) { - var pickColors = []; - var length = instances.length; - - for (var i = 0; i < length; ++i) { - var pickObject = { - primitive : defaultValue(instances[i].pickPrimitive, primitive) - }; - - if (defined(instances[i].id)) { - pickObject.id = instances[i].id; - } - - var pickId = context.createPickId(pickObject); - primitive._pickIds.push(pickId); - pickColors.push(pickId.color); - } - - return pickColors; - } - function getUniformFunction(uniforms, name) { return function() { return uniforms[name]; @@ -846,19 +972,16 @@ define([ var transferableObjects = []; instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; - var allowPicking = primitive.allowPicking; var scene3DOnly = frameState.scene3DOnly; var projection = frameState.mapProjection; var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({ createGeometryResults : primitive._createGeometryResults, instances : instances, - pickIds : allowPicking ? createPickIds(frameState.context, primitive, instances) : undefined, ellipsoid : projection.ellipsoid, projection : projection, elementIndexUintSupported : frameState.context.elementIndexUint, scene3DOnly : scene3DOnly, - allowPicking : allowPicking, vertexCacheOptimize : primitive.vertexCacheOptimize, compressVertices : primitive.compressVertices, modelMatrix : primitive.modelMatrix, @@ -872,30 +995,12 @@ define([ var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult); primitive._geometries = result.geometries; primitive._attributeLocations = result.attributeLocations; - primitive._vaAttributes = result.vaAttributes; - primitive._perInstanceAttributeLocations = result.perInstanceAttributeLocations; primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix); - primitive._validModelMatrix = !Matrix4.equals(primitive.modelMatrix, Matrix4.IDENTITY); primitive._pickOffsets = result.pickOffsets; + primitive._instanceBoundingSpheres = result.boundingSpheres; + primitive._instanceBoundingSpheresCV = result.boundingSpheresCV; - var validInstancesIndices = packedResult.validInstancesIndices; - var invalidInstancesIndices = packedResult.invalidInstancesIndices; - var instanceIds = primitive._instanceIds; - var reorderedInstanceIds = new Array(instanceIds.length); - - var validLength = validInstancesIndices.length; - for (var i = 0; i < validLength; ++i) { - reorderedInstanceIds[i] = instanceIds[validInstancesIndices[i]]; - } - - var invalidLength = invalidInstancesIndices.length; - for (var j = 0; j < invalidLength; ++j) { - reorderedInstanceIds[validLength + j] = instanceIds[invalidInstancesIndices[j]]; - } - - primitive._instanceIds = reorderedInstanceIds; - - if (defined(primitive._geometries)) { + if (defined(primitive._geometries) && primitive._geometries.length > 0) { primitive._state = PrimitiveState.COMBINED; } else { setReady(primitive, frameState, PrimitiveState.FAILED, undefined); @@ -909,11 +1014,7 @@ define([ function loadSynchronous(primitive, frameState) { var instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances]; var length = primitive._numberOfInstances = instances.length; - - var geometries = new Array(length); var clonedInstances = new Array(length); - - var invalidInstances = []; var instanceIds = primitive._instanceIds; var instance; @@ -931,31 +1032,21 @@ define([ createdGeometry = geometry.constructor.createGeometry(geometry); } - if (defined(createdGeometry)) { - geometries[geometryIndex] = createdGeometry; - clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry); - instanceIds.push(instance.id); - } else { - invalidInstances.push(instance); - } + clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry); + instanceIds.push(instance.id); } - geometries.length = geometryIndex; clonedInstances.length = geometryIndex; - var allowPicking = primitive.allowPicking; var scene3DOnly = frameState.scene3DOnly; var projection = frameState.mapProjection; var result = PrimitivePipeline.combineGeometry({ instances : clonedInstances, - invalidInstances : invalidInstances, - pickIds : allowPicking ? createPickIds(frameState.context, primitive, clonedInstances) : undefined, ellipsoid : projection.ellipsoid, projection : projection, elementIndexUintSupported : frameState.context.elementIndexUint, scene3DOnly : scene3DOnly, - allowPicking : allowPicking, vertexCacheOptimize : primitive.vertexCacheOptimize, compressVertices : primitive.compressVertices, modelMatrix : primitive.modelMatrix, @@ -964,18 +1055,12 @@ define([ primitive._geometries = result.geometries; primitive._attributeLocations = result.attributeLocations; - primitive._vaAttributes = result.vaAttributes; - primitive._perInstanceAttributeLocations = result.vaAttributeLocations; primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix); - primitive._validModelMatrix = !Matrix4.equals(primitive.modelMatrix, Matrix4.IDENTITY); primitive._pickOffsets = result.pickOffsets; + primitive._instanceBoundingSpheres = result.boundingSpheres; + primitive._instanceBoundingSpheresCV = result.boundingSpheresCV; - for (i = 0; i < invalidInstances.length; ++i) { - instance = invalidInstances[i]; - instanceIds.push(instance.id); - } - - if (defined(primitive._geometries)) { + if (defined(primitive._geometries) && primitive._geometries.length > 0) { primitive._state = PrimitiveState.COMBINED; } else { setReady(primitive, frameState, PrimitiveState.FAILED, undefined); @@ -985,7 +1070,6 @@ define([ function createVertexArray(primitive, frameState) { var attributeLocations = primitive._attributeLocations; var geometries = primitive._geometries; - var vaAttributes = primitive._vaAttributes; var scene3DOnly = frameState.scene3DOnly; var context = frameState.context; @@ -994,24 +1078,12 @@ define([ for (var i = 0; i < length; ++i) { var geometry = geometries[i]; - var attributes = vaAttributes[i]; - var vaLength = attributes.length; - for (var j = 0; j < vaLength; ++j) { - var attribute = attributes[j]; - attribute.vertexBuffer = Buffer.createVertexBuffer({ - context : context, - typedArray : attribute.values, - usage : BufferUsage.DYNAMIC_DRAW}); - delete attribute.values; - } - va.push(VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : attributeLocations, bufferUsage : BufferUsage.STATIC_DRAW, - interleave : primitive._interleave, - vertexArrayAttributes : attributes + interleave : primitive._interleave })); if (defined(primitive._createBoundingVolumeFunction)) { @@ -1101,17 +1173,22 @@ define([ var attributeLocations = primitive._attributeLocations; - var vs = Primitive._modifyShaderPosition(primitive, appearance.vertexShaderSource, frameState.scene3DOnly); + var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource); + vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly); vs = Primitive._appendShowToShader(primitive, vs); + vs = Primitive._updateColorAttribute(primitive, vs); vs = modifyForEncodedNormals(primitive, vs); var fs = appearance.getFragmentShaderSource(); // Create pick program if (primitive.allowPicking) { + var vsPick = ShaderSource.createPickVertexShaderSource(vs); + vsPick = Primitive._updatePickColorAttribute(vsPick); + primitive._pickSP = ShaderProgram.replaceCache({ context : context, shaderProgram : primitive._pickSP, - vertexShaderSource : ShaderSource.createPickVertexShaderSource(vs), + vertexShaderSource : vsPick, fragmentShaderSource : ShaderSource.createPickFragmentShaderSource(fs, 'varying'), attributeLocations : attributeLocations }); @@ -1157,6 +1234,7 @@ define([ } } var uniforms = combine(appearanceUniformMap, materialUniformMap); + uniforms = primitive._batchTable.getUniformMapCallback()(uniforms); if (defined(primitive.rtcCenter)) { uniforms.u_modifiedModelView = function() { @@ -1227,41 +1305,6 @@ define([ } } - function updatePerInstanceAttributes(primitive) { - if (primitive._dirtyAttributes.length === 0) { - return; - } - - var attributes = primitive._dirtyAttributes; - var length = attributes.length; - for (var i = 0; i < length; ++i) { - var attribute = attributes[i]; - var value = attribute.value; - var indices = attribute.indices; - var indicesLength = indices.length; - for (var j = 0; j < indicesLength; ++j) { - var index = indices[j]; - var offset = index.offset; - var count = index.count; - - var vaAttribute = index.attribute; - var componentDatatype = vaAttribute.componentDatatype; - var componentsPerAttribute = vaAttribute.componentsPerAttribute; - - var typedArray = ComponentDatatype.createTypedArray(componentDatatype, count * componentsPerAttribute); - for (var k = 0; k < count; ++k) { - typedArray.set(value, k * componentsPerAttribute); - } - - var offsetInBytes = offset * componentsPerAttribute * ComponentDatatype.getSizeInBytes(componentDatatype); - vaAttribute.vertexBuffer.copyFromArrayView(typedArray, offsetInBytes); - } - attribute.dirty = false; - } - - attributes.length = 0; - } - function updateBoundingVolumes(primitive, frameState) { // Update bounding volumes for primitives that are sized in pixels. // The pixel size in meters varies based on the distance from the camera. @@ -1358,6 +1401,7 @@ define([ * @exception {DeveloperError} All instance geometries must have the same primitiveType. * @exception {DeveloperError} Appearance and material have a uniform with the same name. * @exception {DeveloperError} Primitive.modelMatrix is only supported in 3D mode. + * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero. */ Primitive.prototype.update = function(frameState) { if (((!defined(this.geometryInstances)) && (this._va.length === 0)) || @@ -1380,6 +1424,17 @@ define([ return; } + var context = frameState.context; + if (!defined(this._batchTable)) { + createBatchTable(this, context); + } + if (this._batchTable.attributes.length > 0) { + if (ContextLimits.maximumVertexTextureImageUnits === 0) { + throw new RuntimeError('Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.'); + } + this._batchTable.update(frameState); + } + if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) { if (this.asynchronous) { loadAsynchronous(this, frameState); @@ -1418,7 +1473,6 @@ define([ createRS = true; } - var context = frameState.context; if (defined(this._material)) { this._material.update(context); } @@ -1440,41 +1494,50 @@ define([ commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState); } - updatePerInstanceAttributes(this); - var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands); updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses); }; - function createGetFunction(name, perInstanceAttributes) { - var attribute = perInstanceAttributes[name]; + function createGetFunction(batchTable, instanceIndex, attributeIndex) { return function() { - if (defined(attribute) && defined(attribute.value)) { - return perInstanceAttributes[name].value; + var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex); + var attribute = batchTable.attributes[attributeIndex]; + var componentsPerAttribute = attribute.componentsPerAttribute; + var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute); + if (defined(attributeValue.constructor.pack)) { + attributeValue.constructor.pack(attributeValue, value, 0); + } else { + value[0] = attributeValue; } - return attribute; + return value; }; } - function createSetFunction(name, perInstanceAttributes, dirtyList) { - return function (value) { + function createSetFunction(batchTable, instanceIndex, attributeIndex) { + return function(value) { //>>includeStart('debug', pragmas.debug); if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) { throw new DeveloperError('value must be and array with length between 1 and 4.'); } //>>includeEnd('debug'); + var attributeValue = getAttributeValue(value); + batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue); + }; + } - var attribute = perInstanceAttributes[name]; - attribute.value = value; - if (!attribute.dirty && attribute.valid) { - dirtyList.push(attribute); - attribute.dirty = true; + function createBoundingSphereProperties(primitive, properties, index) { + properties.boundingSphere = { + get : function() { + return primitive._instanceBoundingSpheres[index]; + } + }; + properties.boundingSphereCV = { + get : function() { + return primitive._instanceBoundingSpheresCV[index]; } }; } - var readOnlyInstanceAttributesScratch = ['boundingSphere', 'boundingSphereCV']; - /** * Returns the modifiable per-instance attributes for a {@link GeometryInstance}. * @@ -1493,7 +1556,7 @@ define([ if (!defined(id)) { throw new DeveloperError('id is required'); } - if (!defined(this._perInstanceAttributeLocations)) { + if (!defined(this._batchTable)) { throw new DeveloperError('must call update before calling getGeometryInstanceAttributes'); } //>>includeEnd('debug'); @@ -1513,34 +1576,26 @@ define([ if (index === -1) { return undefined; } + var attributes = this._perInstanceAttributeCache[index]; if (defined(attributes)) { return attributes; } - var perInstanceAttributes = this._perInstanceAttributeLocations[index]; + var batchTable = this._batchTable; + var perInstanceAttributeIndices = this._batchTableAttributeIndices; attributes = {}; var properties = {}; - var hasProperties = false; - for (var name in perInstanceAttributes) { - if (perInstanceAttributes.hasOwnProperty(name)) { - hasProperties = true; + for (var name in perInstanceAttributeIndices) { + if (perInstanceAttributeIndices.hasOwnProperty(name)) { + var attributeIndex = perInstanceAttributeIndices[name]; properties[name] = { - get : createGetFunction(name, perInstanceAttributes) + get : createGetFunction(batchTable, index, attributeIndex) }; var createSetter = true; - var readOnlyAttributes = readOnlyInstanceAttributesScratch; - length = readOnlyAttributes.length; - for (var j = 0; j < length; ++j) { - if (name === readOnlyInstanceAttributesScratch[j]) { - createSetter = false; - break; - } - } - - readOnlyAttributes = this._readOnlyInstanceAttributes; + var readOnlyAttributes = this._readOnlyInstanceAttributes; if (createSetter && defined(readOnlyAttributes)) { length = readOnlyAttributes.length; for (var k = 0; k < length; ++k) { @@ -1552,14 +1607,13 @@ define([ } if (createSetter) { - properties[name].set = createSetFunction(name, perInstanceAttributes, this._dirtyAttributes); + properties[name].set = createSetFunction(batchTable, index, attributeIndex); } } } - if (hasProperties) { - defineProperties(attributes, properties); - } + createBoundingSphereProperties(this, properties, index); + defineProperties(attributes, properties); this._lastPerInstanceAttributeIndex = index; this._perInstanceAttributeCache[index] = attributes; @@ -1621,14 +1675,14 @@ define([ } this._pickIds = undefined; + this._batchTable = this._batchTable && this._batchTable.destroy(); + //These objects may be fairly large and reference other large objects (like Entities) //We explicitly set them to undefined here so that the memory can be freed //even if a reference to the destroyed Primitive has been kept around. this._instanceIds = undefined; this._perInstanceAttributeCache = undefined; - this._perInstanceAttributeLocations = undefined; this._attributeLocations = undefined; - this._dirtyAttributes = undefined; return destroyObject(this); }; diff --git a/Source/Scene/PrimitivePipeline.js b/Source/Scene/PrimitivePipeline.js index c4986f352daa..a09a764f06e2 100644 --- a/Source/Scene/PrimitivePipeline.js +++ b/Source/Scene/PrimitivePipeline.js @@ -59,7 +59,9 @@ define([ if (toWorld) { for (i = 0; i < length; ++i) { - GeometryPipeline.transformToWorldCoordinates(instances[i]); + if (defined(instances[i].geometry)) { + GeometryPipeline.transformToWorldCoordinates(instances[i]); + } } } else { // Leave geometry in local coordinate system; auto update model-matrix. @@ -67,142 +69,61 @@ define([ } } - function addGeometryPickColor(geometry, pickColor) { + function addGeometryBatchId(geometry, batchId) { var attributes = geometry.attributes; var positionAttr = attributes.position; - var numberOfComponents = 4 * (positionAttr.values.length / positionAttr.componentsPerAttribute); + var numberOfComponents = positionAttr.values.length / positionAttr.componentsPerAttribute; - attributes.pickColor = new GeometryAttribute({ - componentDatatype : ComponentDatatype.UNSIGNED_BYTE, - componentsPerAttribute : 4, - normalize : true, - values : new Uint8Array(numberOfComponents) + attributes.batchId = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 1, + values : new Float32Array(numberOfComponents) }); - var red = Color.floatToByte(pickColor.red); - var green = Color.floatToByte(pickColor.green); - var blue = Color.floatToByte(pickColor.blue); - var alpha = Color.floatToByte(pickColor.alpha); - var values = attributes.pickColor.values; - - for (var j = 0; j < numberOfComponents; j += 4) { - values[j] = red; - values[j + 1] = green; - values[j + 2] = blue; - values[j + 3] = alpha; - } - } - - function addPickColorAttribute(instances, pickIds) { - var length = instances.length; - - for (var i = 0; i < length; ++i) { - var instance = instances[i]; - var pickColor = pickIds[i]; - - if (defined(instance.geometry)) { - addGeometryPickColor(instance.geometry, pickColor); - } else { - addGeometryPickColor(instance.westHemisphereGeometry, pickColor); - addGeometryPickColor(instance.eastHemisphereGeometry, pickColor); - } + var values = attributes.batchId.values; + for (var j = 0; j < numberOfComponents; ++j) { + values[j] = batchId; } } - function getCommonPerInstanceAttributeNames(instances) { + function addBatchIds(instances) { var length = instances.length; - var attributesInAllInstances = []; - var attributes0 = instances[0].attributes; - var name; - - for (name in attributes0) { - if (attributes0.hasOwnProperty(name)) { - var attribute = attributes0[name]; - var inAllInstances = true; - - // Does this same attribute exist in all instances? - for (var i = 1; i < length; ++i) { - var otherAttribute = instances[i].attributes[name]; - - if (!defined(otherAttribute) || - (attribute.componentDatatype !== otherAttribute.componentDatatype) || - (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || - (attribute.normalize !== otherAttribute.normalize)) { - - inAllInstances = false; - break; - } - } - - if (inAllInstances) { - attributesInAllInstances.push(name); - } - } - } - - return attributesInAllInstances; - } - - function addPerInstanceAttributesToGeometry(instanceAttributes, geometry, names) { - var numberOfVertices = Geometry.computeNumberOfVertices(geometry); - - var namesLength = names.length; - for (var j = 0; j < namesLength; ++j) { - var name = names[j]; - var attribute = instanceAttributes[name]; - var componentDatatype = attribute.componentDatatype; - var value = attribute.value; - var componentsPerAttribute = attribute.componentsPerAttribute; - - var buffer = ComponentDatatype.createTypedArray(componentDatatype, numberOfVertices * componentsPerAttribute); - for (var k = 0; k < numberOfVertices; ++k) { - buffer.set(value, k * componentsPerAttribute); - } - - geometry.attributes[name] = new GeometryAttribute({ - componentDatatype : componentDatatype, - componentsPerAttribute : componentsPerAttribute, - normalize : attribute.normalize, - values : buffer - }); - } - } - - function addPerInstanceAttributes(instances, names) { - var length = instances.length; for (var i = 0; i < length; ++i) { var instance = instances[i]; - var instanceAttributes = instance.attributes; - if (defined(instance.geometry)) { - addPerInstanceAttributesToGeometry(instanceAttributes, instance.geometry, names); - } else { - addPerInstanceAttributesToGeometry(instanceAttributes, instance.westHemisphereGeometry, names); - addPerInstanceAttributesToGeometry(instanceAttributes, instance.eastHemisphereGeometry, names); + addGeometryBatchId(instance.geometry, i); + } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) { + addGeometryBatchId(instance.westHemisphereGeometry, i); + addGeometryBatchId(instance.eastHemisphereGeometry, i); } } } function geometryPipeline(parameters) { var instances = parameters.instances; - var pickIds = parameters.pickIds; var projection = parameters.projection; var uintIndexSupport = parameters.elementIndexUintSupported; var scene3DOnly = parameters.scene3DOnly; - var allowPicking = parameters.allowPicking; var vertexCacheOptimize = parameters.vertexCacheOptimize; var compressVertices = parameters.compressVertices; var modelMatrix = parameters.modelMatrix; var i; var geometry; + var primitiveType; var length = instances.length; - var primitiveType = instances[0].geometry.primitiveType; + + for (i = 0 ; i < length; ++i) { + if (defined(instances[i].geometry)) { + primitiveType = instances[i].geometry.primitiveType; + break; + } + } //>>includeStart('debug', pragmas.debug); for (i = 1; i < length; ++i) { - if (instances[i].geometry.primitiveType !== primitiveType) { + if (defined(instances[i].geometry) && instances[i].geometry.primitiveType !== primitiveType) { throw new DeveloperError('All instance geometries must have the same primitiveType.'); } } @@ -214,18 +135,13 @@ define([ // Clip to IDL if (!scene3DOnly) { for (i = 0; i < length; ++i) { - GeometryPipeline.splitLongitude(instances[i]); + if (defined(instances[i].geometry)) { + GeometryPipeline.splitLongitude(instances[i]); + } } } - // Add pickColor attribute for picking individual instances - if (allowPicking) { - addPickColorAttribute(instances, pickIds); - } - - // add attributes to the geometry for each per-instance attribute - var perInstanceAttributeNames = getCommonPerInstanceAttributeNames(instances); - addPerInstanceAttributes(instances, perInstanceAttributeNames); + addBatchIds(instances); // Optimize for vertex shader caches if (vertexCacheOptimize) { @@ -234,7 +150,7 @@ define([ if (defined(instance.geometry)) { GeometryPipeline.reorderForPostVertexCache(instance.geometry); GeometryPipeline.reorderForPreVertexCache(instance.geometry); - } else { + } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) { GeometryPipeline.reorderForPostVertexCache(instance.westHemisphereGeometry); GeometryPipeline.reorderForPreVertexCache(instance.westHemisphereGeometry); @@ -299,159 +215,6 @@ define([ return geometries; } - function createPerInstanceVAAttributes(geometry, attributeLocations, names) { - var vaAttributes = []; - var attributes = geometry.attributes; - - var length = names.length; - for (var i = 0; i < length; ++i) { - var name = names[i]; - var attribute = attributes[name]; - - var componentDatatype = attribute.componentDatatype; - if (componentDatatype === ComponentDatatype.DOUBLE) { - componentDatatype = ComponentDatatype.FLOAT; - } - - var typedArray = ComponentDatatype.createTypedArray(componentDatatype, attribute.values); - vaAttributes.push({ - index : attributeLocations[name], - componentDatatype : componentDatatype, - componentsPerAttribute : attribute.componentsPerAttribute, - normalize : attribute.normalize, - values : typedArray - }); - - delete attributes[name]; - } - - return vaAttributes; - } - - function computePerInstanceAttributeLocationsForGeometry(instanceIndex, geometry, instanceAttributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices) { - var numberOfVertices = Geometry.computeNumberOfVertices(geometry); - - if (!defined(indices[instanceIndex])) { - indices[instanceIndex] = { - boundingSphere : geometry.boundingSphere, - boundingSphereCV : geometry.boundingSphereCV - }; - } - - var namesLength = names.length; - for (var j = 0; j < namesLength; ++j) { - var name = names[j]; - var index = attributeLocations[name]; - - var tempVertexCount = numberOfVertices; - while (tempVertexCount > 0) { - var vaIndex = defaultValue(vaIndices[name], 0); - var va = vertexArrays[vaIndex]; - var vaLength = va.length; - - var attribute; - for (var k = 0; k < vaLength; ++k) { - attribute = va[k]; - if (attribute.index === index) { - break; - } - } - - if (!defined(indices[instanceIndex][name])) { - indices[instanceIndex][name] = { - dirty : false, - valid : true, - value : instanceAttributes[name].value, - indices : [] - }; - } - - var size = attribute.values.length / attribute.componentsPerAttribute; - var offset = defaultValue(offsets[name], 0); - - var count; - if (offset + tempVertexCount < size) { - count = tempVertexCount; - indices[instanceIndex][name].indices.push({ - attribute : attribute, - offset : offset, - count : count - }); - offsets[name] = offset + tempVertexCount; - } else { - count = size - offset; - indices[instanceIndex][name].indices.push({ - attribute : attribute, - offset : offset, - count : count - }); - offsets[name] = 0; - vaIndices[name] = vaIndex + 1; - } - - tempVertexCount -= count; - } - } - } - - function computePerInstanceAttributeLocations(instances, invalidInstances, vertexArrays, attributeLocations, names) { - var indices = []; - - var length = instances.length; - var offsets = {}; - var vaIndices = {}; - - var i; - var instance; - var attributes; - - for (i = 0; i < length; ++i) { - instance = instances[i]; - attributes = instance.attributes; - if (defined(instance.geometry)) { - computePerInstanceAttributeLocationsForGeometry(i, instance.geometry, attributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices); - } - } - - for (i = 0; i < length; ++i) { - instance = instances[i]; - attributes = instance.attributes; - if (defined(instance.westHemisphereGeometry)) { - computePerInstanceAttributeLocationsForGeometry(i, instance.westHemisphereGeometry, attributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices); - } - } - - for (i = 0; i < length; ++i) { - instance = instances[i]; - attributes = instance.attributes; - if (defined(instance.eastHemisphereGeometry)) { - computePerInstanceAttributeLocationsForGeometry(i, instance.eastHemisphereGeometry, attributes, names, attributeLocations, vertexArrays, indices, offsets, vaIndices); - } - } - - length = invalidInstances.length; - for (i = 0; i < length; ++i) { - instance = invalidInstances[i]; - attributes = instance.attributes; - - var instanceAttributes = {}; - indices.push(instanceAttributes); - - var namesLength = names.length; - for (var j = 0; j < namesLength; ++j) { - var name = names[j]; - instanceAttributes[name] = { - dirty : false, - valid : false, - value : attributes[name].value, - indices : [] - }; - } - } - - return indices; - } - function createPickOffsets(instances, geometryName, geometries, pickOffsets) { var offset; var indexCount; @@ -512,44 +275,50 @@ define([ PrimitivePipeline.combineGeometry = function(parameters) { var geometries; var attributeLocations; - var perInstanceAttributes; - var perInstanceAttributeNames; - var length; - var instances = parameters.instances; - var invalidInstances = parameters.invalidInstances; + var length = instances.length; - if (instances.length > 0) { + if (length > 0) { geometries = geometryPipeline(parameters); - attributeLocations = GeometryPipeline.createAttributeLocations(geometries[0]); - - perInstanceAttributeNames = getCommonPerInstanceAttributeNames(instances); - - perInstanceAttributes = []; - length = geometries.length; - for (var i = 0; i < length; ++i) { - var geometry = geometries[i]; - perInstanceAttributes.push(createPerInstanceVAAttributes(geometry, attributeLocations, perInstanceAttributeNames)); + if (geometries.length > 0) { + attributeLocations = GeometryPipeline.createAttributeLocations(geometries[0]); } } - perInstanceAttributeNames = defined(perInstanceAttributeNames) ? perInstanceAttributeNames : getCommonPerInstanceAttributeNames(invalidInstances); - var indices = computePerInstanceAttributeLocations(instances, invalidInstances, perInstanceAttributes, attributeLocations, perInstanceAttributeNames); - var pickOffsets; - if (parameters.createPickOffsets && defined(geometries)) { + if (parameters.createPickOffsets && geometries.length > 0) { pickOffsets = createInstancePickOffsets(instances, geometries); } + var boundingSpheres = new Array(length); + var boundingSpheresCV = new Array(length); + for (var i = 0; i < length; ++i) { + var instance = instances[i]; + var geometry = instance.geometry; + if (defined(geometry)) { + boundingSpheres[i] = geometry.boundingSphere; + boundingSpheresCV[i] = geometry.boundingSphereCV; + } + + var eastHemisphereGeometry = instance.eastHemisphereGeometry; + var westHemisphereGeometry = instance.westHemisphereGeometry; + if (defined(eastHemisphereGeometry) && defined(westHemisphereGeometry)) { + if (defined(eastHemisphereGeometry.boundingSphere) && defined(westHemisphereGeometry.boundingSphere)) { + boundingSpheres[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphere, westHemisphereGeometry.boundingSphere); + } + if (defined(eastHemisphereGeometry.boundingSphereCV) && defined(westHemisphereGeometry.boundingSphereCV)) { + boundingSpheresCV[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphereCV, westHemisphereGeometry.boundingSphereCV); + } + } + } + return { geometries : geometries, modelMatrix : parameters.modelMatrix, attributeLocations : attributeLocations, - vaAttributes : perInstanceAttributes, - vaAttributeLocations : indices, - validInstancesIndices : parameters.validInstancesIndices, - invalidInstancesIndices : parameters.invalidInstancesIndices, - pickOffsets : pickOffsets + pickOffsets : pickOffsets, + boundingSpheres : boundingSpheres, + boundingSpheresCV : boundingSpheresCV }; }; @@ -577,20 +346,6 @@ define([ } } - /** - * @private - */ - function transferPerInstanceAttributes(perInstanceAttributes, transferableObjects) { - var length = perInstanceAttributes.length; - for (var i = 0; i < length; ++i) { - var vaAttributes = perInstanceAttributes[i]; - var vaLength = vaAttributes.length; - for (var j = 0; j < vaLength; ++j) { - transferableObjects.push(vaAttributes[j].values.buffer); - } - } - } - // This function was created by simplifying packCreateGeometryResults into a count-only operation. function countCreateGeometryResults(items) { var count = 1; @@ -779,6 +534,7 @@ define([ primitiveType : primitiveType, geometryType : geometryType, boundingSphere : boundingSphere, + boundingSphereCV : boundingSphereCV, indices : indices, attributes : attributes }); @@ -787,47 +543,9 @@ define([ return result; }; - function packPickIds(pickIds, transferableObjects) { - var length = pickIds.length; - var packedPickIds = new Uint32Array(pickIds.length); - for (var i = 0; i < length; ++i) { - packedPickIds[i] = pickIds[i].toRgba(); - } - transferableObjects.push(packedPickIds.buffer); - return packedPickIds; - } - - function unpackPickIds(packedPickIds) { - var length = packedPickIds.length; - var pickIds = new Array(length); - for (var i = 0; i < length; i++) { - pickIds[i] = Color.fromRgba(packedPickIds[i]); - } - return pickIds; - } - - // This function was created by simplifying packInstancesForCombine into a count-only operation. - function countInstancesForCombine(instances) { - var length = instances.length; - var count = 1 + (length * 17); - for (var i = 0; i < length; i++) { - var attributes = instances[i].attributes; - for ( var property in attributes) { - if (attributes.hasOwnProperty(property) && defined(attributes[property])) { - var attribute = attributes[property]; - count += 5 + attribute.value.length; - } - } - } - return count; - } - function packInstancesForCombine(instances, transferableObjects) { - var packedData = new Float64Array(countInstancesForCombine(instances)); - var stringHash = {}; - var stringTable = []; - var length = instances.length; + var packedData = new Float64Array(1 + (length * 16)); var count = 0; packedData[count++] = length; for (var i = 0; i < length; i++) { @@ -835,43 +553,14 @@ define([ Matrix4.pack(instance.modelMatrix, packedData, count); count += Matrix4.packedLength; - - var attributes = instance.attributes; - var attributesToWrite = []; - for ( var property in attributes) { - if (attributes.hasOwnProperty(property) && defined(attributes[property])) { - attributesToWrite.push(property); - if (!defined(stringHash[property])) { - stringHash[property] = stringTable.length; - stringTable.push(property); - } - } - } - - packedData[count++] = attributesToWrite.length; - for (var q = 0; q < attributesToWrite.length; q++) { - var name = attributesToWrite[q]; - var attribute = attributes[name]; - packedData[count++] = stringHash[name]; - packedData[count++] = attribute.componentDatatype; - packedData[count++] = attribute.componentsPerAttribute; - packedData[count++] = attribute.normalize; - packedData[count++] = attribute.value.length; - packedData.set(attribute.value, count); - count += attribute.value.length; - } } transferableObjects.push(packedData.buffer); - return { - stringTable : stringTable, - packedData : packedData - }; + return packedData; } function unpackInstancesForCombine(data) { - var packedInstances = data.packedData; - var stringTable = data.stringTable; + var packedInstances = data; var result = new Array(packedInstances[0]); var count = 0; @@ -880,29 +569,7 @@ define([ var modelMatrix = Matrix4.unpack(packedInstances, i); i += Matrix4.packedLength; - var attributes = {}; - var numAttributes = packedInstances[i++]; - for (var x = 0; x < numAttributes; x++) { - var name = stringTable[packedInstances[i++]]; - var componentDatatype = packedInstances[i++]; - var componentsPerAttribute = packedInstances[i++]; - var normalize = packedInstances[i++] !== 0; - var length = packedInstances[i++]; - var value = ComponentDatatype.createTypedArray(componentDatatype, length); - for (var valueIndex = 0; valueIndex < length; valueIndex++) { - value[valueIndex] = packedInstances[i++]; - } - - attributes[name] = { - componentDatatype : componentDatatype, - componentsPerAttribute : componentsPerAttribute, - normalize : normalize, - value : value - }; - } - result[count++] = { - attributes : attributes, modelMatrix : modelMatrix }; } @@ -910,163 +577,6 @@ define([ return result; } - // This function was created by simplifying packAttributeLocations into a count-only operation. - function countAttributeLocations(attributeLocations) { - var length = attributeLocations.length; - var count = 1 + length; - for (var i = 0; i < length; i++) { - var instance = attributeLocations[i]; - - count += 2; - count += defined(instance.boundingSphere) ? BoundingSphere.packedLength : 0.0; - count += defined(instance.boundingSphereCV) ? BoundingSphere.packedLength : 0.0; - - for ( var propertyName in instance) { - if (instance.hasOwnProperty(propertyName) && defined(instance[propertyName]) && - propertyName !== 'boundingSphere' && propertyName !== 'boundingSphereCV') { - var property = instance[propertyName]; - count += 4 + (property.indices.length * 3) + property.value.length; - } - } - } - return count; - } - - function packAttributeLocations(attributeLocations, transferableObjects) { - var packedData = new Float64Array(countAttributeLocations(attributeLocations)); - var stringTable = []; - var attributeTable = []; - - var stringHash = {}; - var length = attributeLocations.length; - var count = 0; - packedData[count++] = length; - for (var i = 0; i < length; i++) { - var instance = attributeLocations[i]; - - var boundingSphere = instance.boundingSphere; - var hasBoundingSphere = defined(boundingSphere); - packedData[count++] = hasBoundingSphere ? 1.0 : 0.0; - if (hasBoundingSphere) { - BoundingSphere.pack(boundingSphere, packedData, count); - count += BoundingSphere.packedLength; - } - - boundingSphere = instance.boundingSphereCV; - hasBoundingSphere = defined(boundingSphere); - packedData[count++] = hasBoundingSphere ? 1.0 : 0.0; - if (hasBoundingSphere) { - BoundingSphere.pack(boundingSphere, packedData, count); - count += BoundingSphere.packedLength; - } - - var propertiesToWrite = []; - for ( var propertyName in instance) { - if (instance.hasOwnProperty(propertyName) && defined(instance[propertyName]) && - propertyName !== 'boundingSphere' && propertyName !== 'boundingSphereCV') { - propertiesToWrite.push(propertyName); - if (!defined(stringHash[propertyName])) { - stringHash[propertyName] = stringTable.length; - stringTable.push(propertyName); - } - } - } - - packedData[count++] = propertiesToWrite.length; - for (var q = 0; q < propertiesToWrite.length; q++) { - var name = propertiesToWrite[q]; - var property = instance[name]; - packedData[count++] = stringHash[name]; - packedData[count++] = property.valid ? 1.0 : 0.0; - - var indices = property.indices; - var indicesLength = indices.length; - packedData[count++] = indicesLength; - for (var x = 0; x < indicesLength; x++) { - var index = indices[x]; - packedData[count++] = index.count; - packedData[count++] = index.offset; - var tableIndex = attributeTable.indexOf(index.attribute); - if (tableIndex === -1) { - tableIndex = attributeTable.length; - attributeTable.push(index.attribute); - } - packedData[count++] = tableIndex; - } - - packedData[count++] = property.value.length; - packedData.set(property.value, count); - count += property.value.length; - } - } - - transferableObjects.push(packedData.buffer); - - return { - stringTable : stringTable, - packedData : packedData, - attributeTable : attributeTable - }; - } - - function unpackAttributeLocations(packedAttributeLocations, vaAttributes) { - var stringTable = packedAttributeLocations.stringTable; - var attributeTable = packedAttributeLocations.attributeTable; - var packedData = packedAttributeLocations.packedData; - - var attributeLocations = new Array(packedData[0]); - var attributeLocationsIndex = 0; - var i = 1; - var packedDataLength = packedData.length; - while (i < packedDataLength) { - var instance = {}; - - var hasBoundingSphere = packedData[i++] === 1.0; - if (hasBoundingSphere) { - instance.boundingSphere = BoundingSphere.unpack(packedData, i); - i += BoundingSphere.packedLength; - } - - hasBoundingSphere = packedData[i++] === 1.0; - if (hasBoundingSphere) { - instance.boundingSphereCV = BoundingSphere.unpack(packedData, i); - i += BoundingSphere.packedLength; - } - - var numAttributes = packedData[i++]; - for (var x = 0; x < numAttributes; x++) { - var name = stringTable[packedData[i++]]; - var valid = packedData[i++] === 1.0; - - var indicesLength = packedData[i++]; - var indices = indicesLength > 0 ? new Array(indicesLength) : undefined; - for (var indicesIndex = 0; indicesIndex < indicesLength; indicesIndex++) { - var index = {}; - index.count = packedData[i++]; - index.offset = packedData[i++]; - index.attribute = attributeTable[packedData[i++]]; - indices[indicesIndex] = index; - } - - var valueLength = packedData[i++]; - var value = valid ? ComponentDatatype.createTypedArray(indices[0].attribute.componentDatatype, valueLength) : new Array(valueLength); - for (var valueIndex = 0; valueIndex < valueLength; valueIndex++) { - value[valueIndex] = packedData[i++]; - } - - instance[name] = { - dirty : false, - valid : valid, - indices : indices, - value : value - }; - } - attributeLocations[attributeLocationsIndex++] = instance; - } - - return attributeLocations; - } - /** * @private */ @@ -1078,20 +588,13 @@ define([ transferableObjects.push(createGeometryResults[i].packedData.buffer); } - var packedPickIds; - if (parameters.allowPicking) { - packedPickIds = packPickIds(parameters.pickIds, transferableObjects); - } - return { createGeometryResults : parameters.createGeometryResults, packedInstances : packInstancesForCombine(parameters.instances, transferableObjects), - packedPickIds : packedPickIds, ellipsoid : parameters.ellipsoid, isGeographic : parameters.projection instanceof GeographicProjection, elementIndexUintSupported : parameters.elementIndexUintSupported, scene3DOnly : parameters.scene3DOnly, - allowPicking : parameters.allowPicking, vertexCacheOptimize : parameters.vertexCacheOptimize, compressVertices : parameters.compressVertices, modelMatrix : parameters.modelMatrix, @@ -1104,37 +607,17 @@ define([ */ PrimitivePipeline.unpackCombineGeometryParameters = function(packedParameters) { var instances = unpackInstancesForCombine(packedParameters.packedInstances); - var allowPicking = packedParameters.allowPicking; - var pickIds = allowPicking ? unpackPickIds(packedParameters.packedPickIds) : undefined; var createGeometryResults = packedParameters.createGeometryResults; var length = createGeometryResults.length; var instanceIndex = 0; - var validInstances = []; - var invalidInstances = []; - var validInstancesIndices = []; - var invalidInstancesIndices = []; - var validPickIds = []; - for (var resultIndex = 0; resultIndex < length; resultIndex++) { var geometries = PrimitivePipeline.unpackCreateGeometryResults(createGeometryResults[resultIndex]); var geometriesLength = geometries.length; for (var geometryIndex = 0; geometryIndex < geometriesLength; geometryIndex++) { var geometry = geometries[geometryIndex]; var instance = instances[instanceIndex]; - - if (defined(geometry)) { - instance.geometry = geometry; - validInstances.push(instance); - validInstancesIndices.push(instanceIndex); - if (allowPicking) { - validPickIds.push(pickIds[instanceIndex]); - } - } else { - invalidInstances.push(instance); - invalidInstancesIndices.push(instanceIndex); - } - + instance.geometry = geometry; ++instanceIndex; } } @@ -1143,16 +626,11 @@ define([ var projection = packedParameters.isGeographic ? new GeographicProjection(ellipsoid) : new WebMercatorProjection(ellipsoid); return { - instances : validInstances, - invalidInstances : invalidInstances, - validInstancesIndices : validInstancesIndices, - invalidInstancesIndices : invalidInstancesIndices, - pickIds : validPickIds, + instances : instances, ellipsoid : ellipsoid, projection : projection, elementIndexUintSupported : packedParameters.elementIndexUintSupported, scene3DOnly : packedParameters.scene3DOnly, - allowPicking : packedParameters.allowPicking, vertexCacheOptimize : packedParameters.vertexCacheOptimize, compressVertices : packedParameters.compressVertices, modelMatrix : Matrix4.clone(packedParameters.modelMatrix), @@ -1160,24 +638,62 @@ define([ }; }; + function packBoundingSpheres(boundingSpheres) { + var length = boundingSpheres.length; + var bufferLength = 1 + (BoundingSphere.packedLength + 1) * length; + var buffer = new Float32Array(bufferLength); + + var bufferIndex = 0; + buffer[bufferIndex++] = length; + + for (var i = 0; i < length; ++i) { + var bs = boundingSpheres[i]; + if (!defined(bs)) { + buffer[bufferIndex++] = 0.0; + } else { + buffer[bufferIndex++] = 1.0; + BoundingSphere.pack(boundingSpheres[i], buffer, bufferIndex); + } + bufferIndex += BoundingSphere.packedLength; + } + + return buffer; + } + + function unpackBoundingSpheres(buffer) { + var result = new Array(buffer[0]); + var count = 0; + + var i = 1; + while (i < buffer.length) { + if (buffer[i++] === 1.0) { + result[count++] = BoundingSphere.unpack(buffer, i); + } + i += BoundingSphere.packedLength; + } + + return result; + } + /** * @private */ PrimitivePipeline.packCombineGeometryResults = function(results, transferableObjects) { if (defined(results.geometries)) { transferGeometries(results.geometries, transferableObjects); - transferPerInstanceAttributes(results.vaAttributes, transferableObjects); } + var packedBoundingSpheres = packBoundingSpheres(results.boundingSpheres); + var packedBoundingSpheresCV = packBoundingSpheres(results.boundingSpheresCV); + transferableObjects.push(packedBoundingSpheres.buffer, packedBoundingSpheresCV.buffer); + return { geometries : results.geometries, attributeLocations : results.attributeLocations, - vaAttributes : results.vaAttributes, - packedVaAttributeLocations : packAttributeLocations(results.vaAttributeLocations, transferableObjects), modelMatrix : results.modelMatrix, - validInstancesIndices : results.validInstancesIndices, - invalidInstancesIndices : results.invalidInstancesIndices, - pickOffsets : results.pickOffsets + pickOffsets : results.pickOffsets, + boundingSpheres : packedBoundingSpheres, + boundingSpheresCV : packedBoundingSpheresCV }; }; @@ -1188,10 +704,10 @@ define([ return { geometries : packedResult.geometries, attributeLocations : packedResult.attributeLocations, - vaAttributes : packedResult.vaAttributes, - perInstanceAttributeLocations : unpackAttributeLocations(packedResult.packedVaAttributeLocations, packedResult.vaAttributes), modelMatrix : packedResult.modelMatrix, - pickOffsets : packedResult.pickOffsets + pickOffsets : packedResult.pickOffsets, + boundingSpheres : unpackBoundingSpheres(packedResult.boundingSpheres), + boundingSpheresCV : unpackBoundingSpheres(packedResult.boundingSpheresCV) }; }; diff --git a/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl index 59d74a27b25a..72e2081604ef 100644 --- a/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/AllMaterialAppearanceVS.glsl @@ -4,6 +4,7 @@ attribute vec3 normal; attribute vec3 tangent; attribute vec3 binormal; attribute vec2 st; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl index 6fe9772ba516..0d496a877e7c 100644 --- a/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/BasicMaterialAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl index 61f12dcb32e2..1bd13c9a5382 100644 --- a/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl +++ b/Source/Shaders/Appearances/EllipsoidSurfaceAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec2 st; +attribute float batchId; varying vec3 v_positionMC; varying vec3 v_positionEC; diff --git a/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl b/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl index aa8de58a0ba9..b429e64c6896 100644 --- a/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PerInstanceColorAppearanceVS.glsl @@ -2,6 +2,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; attribute vec4 color; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl index 44c759da268e..ce015c678423 100644 --- a/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PerInstanceFlatColorAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec4 color; +attribute float batchId; varying vec4 v_color; diff --git a/Source/Shaders/Appearances/PointAppearanceVS.glsl b/Source/Shaders/Appearances/PointAppearanceVS.glsl index dd4a639ea7fa..4d41d523be98 100644 --- a/Source/Shaders/Appearances/PointAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PointAppearanceVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 color; +attribute float batchId; uniform float pointSize; diff --git a/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl b/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl index f096e4a0d676..db7c610d10d6 100644 --- a/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PolylineColorAppearanceVS.glsl @@ -6,6 +6,7 @@ attribute vec3 nextPosition3DHigh; attribute vec3 nextPosition3DLow; attribute vec2 expandAndWidth; attribute vec4 color; +attribute float batchId; varying vec4 v_color; diff --git a/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl index 9148abc52bce..dcb40b5ad322 100644 --- a/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/PolylineMaterialAppearanceVS.glsl @@ -6,6 +6,7 @@ attribute vec3 nextPosition3DHigh; attribute vec3 nextPosition3DLow; attribute vec2 expandAndWidth; attribute vec2 st; +attribute float batchId; varying float v_width; varying vec2 v_st; diff --git a/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl b/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl index b606f12a0ef1..19c102bba321 100644 --- a/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl +++ b/Source/Shaders/Appearances/TexturedMaterialAppearanceVS.glsl @@ -2,6 +2,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec3 normal; attribute vec2 st; +attribute float batchId; varying vec3 v_positionEC; varying vec3 v_normalEC; diff --git a/Source/Shaders/ShadowVolumeVS.glsl b/Source/Shaders/ShadowVolumeVS.glsl index 81b3c90294c6..a97b96c1752b 100644 --- a/Source/Shaders/ShadowVolumeVS.glsl +++ b/Source/Shaders/ShadowVolumeVS.glsl @@ -1,6 +1,7 @@ attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec4 color; +attribute float batchId; // emulated noperspective varying float v_WindowZ; diff --git a/Specs/Scene/DebugAppearanceSpec.js b/Specs/Scene/DebugAppearanceSpec.js index 591fa791a495..229e31d72582 100644 --- a/Specs/Scene/DebugAppearanceSpec.js +++ b/Specs/Scene/DebugAppearanceSpec.js @@ -67,7 +67,8 @@ defineSuite([ it('default construct with normal, binormal, or tangent attribute name', function() { var a = new DebugAppearance({ - attributeName : 'normal' + attributeName : 'normal', + perInstanceAttribute : false }); expect(a.vertexShaderSource).toBeDefined(); @@ -87,7 +88,8 @@ defineSuite([ it('default construct with st attribute name', function() { var a = new DebugAppearance({ - attributeName : 'st' + attributeName : 'st', + perInstanceAttribute : false }); expect(a.vertexShaderSource).toBeDefined(); @@ -108,7 +110,8 @@ defineSuite([ it('debug appearance with float attribute name', function() { var a = new DebugAppearance({ attributeName : 'rotation', - glslDatatype : 'float' + glslDatatype : 'float', + perInstanceAttribute : true }); expect(a.vertexShaderSource).toBeDefined(); @@ -129,7 +132,8 @@ defineSuite([ it('debug appearance with vec3 attribute name', function() { var a = new DebugAppearance({ attributeName : 'str', - glslDatatype : 'vec3' + glslDatatype : 'vec3', + perInstanceAttribute : false }); expect(a.vertexShaderSource).toBeDefined(); @@ -150,7 +154,8 @@ defineSuite([ it('debug appearance with vec4 attribute name', function() { var a = new DebugAppearance({ attributeName : 'quaternion', - glslDatatype : 'vec4' + glslDatatype : 'vec4', + perInstanceAttribute : true }); expect(a.vertexShaderSource).toBeDefined(); @@ -172,7 +177,8 @@ defineSuite([ expect(function() { return new DebugAppearance({ attributeName : 'invalid_datatype', - glslDatatype : 'invalid' + glslDatatype : 'invalid', + perInstanceAttribute : true }); }).toThrowDeveloperError(); }); @@ -185,7 +191,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'normal' + attributeName : 'normal', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -206,7 +213,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'binormal' + attributeName : 'binormal', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -227,7 +235,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'tangent' + attributeName : 'tangent', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -247,7 +256,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ - attributeName : 'st' + attributeName : 'st', + perInstanceAttribute : false }), asynchronous : false, compressVertices : false @@ -272,7 +282,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'float' + glslDatatype : 'float', + perInstanceAttribute : true }), asynchronous : false }); @@ -296,7 +307,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'vec2' + glslDatatype : 'vec2', + perInstanceAttribute : true }), asynchronous : false }); @@ -320,7 +332,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'vec3' + glslDatatype : 'vec3', + perInstanceAttribute : true }), asynchronous : false }); @@ -344,7 +357,8 @@ defineSuite([ geometryInstances : rectangleInstance, appearance : new DebugAppearance({ attributeName : 'debug', - glslDatatype : 'vec4' + glslDatatype : 'vec4', + perInstanceAttribute : true }), asynchronous : false });