defineSuite([ 'Core/ComponentDatatype', 'Core/Geometry', 'Core/GeometryAttribute', 'Core/GeometryPipeline', 'Core/IndexDatatype', 'Core/PrimitiveType', 'Renderer/BufferUsage', 'Renderer/ClearCommand', 'Renderer/DrawCommand', 'Renderer/ShaderProgram', 'Renderer/VertexArray', 'Specs/createContext' ], 'Renderer/VertexArrayFactory', function( ComponentDatatype, Geometry, GeometryAttribute, GeometryPipeline, IndexDatatype, PrimitiveType, BufferUsage, ClearCommand, DrawCommand, ShaderProgram, VertexArray, createContext) { 'use strict'; var context; var va; var sp; beforeAll(function() { context = createContext(); }); afterAll(function() { context.destroyForSpecs(); }); afterEach(function() { va = va && va.destroy(); sp = sp && sp.destroy(); }); it('throws when there is no context', function() { expect(function() { return VertexArray.fromGeometry(); }).toThrowDeveloperError(); }); it('creates with no optional arguments', function() { va = VertexArray.fromGeometry({ context : context }); expect(va.numberOfAttributes).toEqual(0); expect(va.indexBuffer).not.toBeDefined(); }); it('creates with no geometry', function() { va = VertexArray.fromGeometry({ context : context, interleave : true }); expect(va.numberOfAttributes).toEqual(0); expect(va.indexBuffer).not.toBeDefined(); }); it('creates a single-attribute vertex (non-interleaved)', function() { var geometry = new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 1.0, 1.0, 1.0] }) }, primitiveType : PrimitiveType.POINTS }); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : GeometryPipeline.createAttributeLocations(geometry) }); expect(va.numberOfAttributes).toEqual(1); expect(va.indexBuffer).not.toBeDefined(); var position = geometry.attributes.position; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(0); // Tightly packed expect(va.getAttribute(0).vertexBuffer.usage).toEqual(BufferUsage.DYNAMIC_DRAW); // Default }); it('creates a single-attribute vertex (interleaved)', function() { var geometry = new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 1.0, 1.0, 1.0] }) }, primitiveType : PrimitiveType.POINTS }); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : GeometryPipeline.createAttributeLocations(geometry), interleave : true, bufferUsage : BufferUsage.STATIC_DRAW }); expect(va.numberOfAttributes).toEqual(1); expect(va.indexBuffer).not.toBeDefined(); var position = geometry.attributes.position; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(ComponentDatatype.getSizeInBytes(position.componentDatatype) * position.componentsPerAttribute); expect(va.getAttribute(0).vertexBuffer.usage).toEqual(BufferUsage.STATIC_DRAW); }); it('creates a homogeneous multiple-attribute vertex (non-interleaved)', function() { var geometry = new Geometry({ attributes : { customPosition : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 2.0, 2.0, 2.0] }), customNormal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [1.0, 1.0, 1.0, 3.0, 3.0, 3.0] }) }, primitiveType : PrimitiveType.POINTS }); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : GeometryPipeline.createAttributeLocations(geometry) }); expect(va.numberOfAttributes).toEqual(2); expect(va.indexBuffer).not.toBeDefined(); var position = geometry.attributes.customPosition; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(0); // Tightly packed var normal = geometry.attributes.customNormal; expect(va.getAttribute(1).index).toEqual(1); expect(va.getAttribute(1).componentDatatype).toEqual(normal.componentDatatype); expect(va.getAttribute(1).componentsPerAttribute).toEqual(normal.componentsPerAttribute); expect(va.getAttribute(1).offsetInBytes).toEqual(0); expect(va.getAttribute(1).strideInBytes).toEqual(0); // Tightly packed expect(va.getAttribute(0).vertexBuffer).not.toBe(va.getAttribute(1).vertexBuffer); }); it('creates a homogeneous multiple-attribute vertex (interleaved)', function() { var geometry = new Geometry({ attributes : { customPosition : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 2.0, 2.0, 2.0] }), customNormal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [1.0, 1.0, 1.0, 3.0, 3.0, 3.0] }) }, primitiveType : PrimitiveType.POINTS }); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : GeometryPipeline.createAttributeLocations(geometry), interleave : true }); expect(va.numberOfAttributes).toEqual(2); expect(va.indexBuffer).not.toBeDefined(); var position = geometry.attributes.customPosition; var normal = geometry.attributes.customNormal; var expectedStride = ComponentDatatype.getSizeInBytes(position.componentDatatype) * position.componentsPerAttribute + ComponentDatatype.getSizeInBytes(normal.componentDatatype) * normal.componentsPerAttribute; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(expectedStride); expect(va.getAttribute(1).index).toEqual(1); expect(va.getAttribute(1).componentDatatype).toEqual(normal.componentDatatype); expect(va.getAttribute(1).componentsPerAttribute).toEqual(normal.componentsPerAttribute); expect(va.getAttribute(1).offsetInBytes).toEqual(ComponentDatatype.getSizeInBytes(position.componentDatatype) * position.componentsPerAttribute); expect(va.getAttribute(1).strideInBytes).toEqual(expectedStride); expect(va.getAttribute(0).vertexBuffer).toBe(va.getAttribute(1).vertexBuffer); }); it('creates a heterogeneous multiple-attribute vertex (interleaved)', function() { var geometry = new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 2.0, 2.0, 2.0] }), colors : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 4, values : [1, 1, 1, 1, 2, 2, 2, 2] }) }, primitiveType : PrimitiveType.POINTS }); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : GeometryPipeline.createAttributeLocations(geometry), interleave : true }); expect(va.numberOfAttributes).toEqual(2); expect(va.indexBuffer).not.toBeDefined(); var position = geometry.attributes.position; var colors = geometry.attributes.colors; var expectedStride = ComponentDatatype.getSizeInBytes(position.componentDatatype) * position.componentsPerAttribute + ComponentDatatype.getSizeInBytes(colors.componentDatatype) * colors.componentsPerAttribute; expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).componentDatatype).toEqual(position.componentDatatype); expect(va.getAttribute(0).componentsPerAttribute).toEqual(position.componentsPerAttribute); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(expectedStride); expect(va.getAttribute(1).index).toEqual(1); expect(va.getAttribute(1).componentDatatype).toEqual(colors.componentDatatype); expect(va.getAttribute(1).componentsPerAttribute).toEqual(colors.componentsPerAttribute); expect(va.getAttribute(1).offsetInBytes).toEqual(ComponentDatatype.getSizeInBytes(position.componentDatatype) * position.componentsPerAttribute); expect(va.getAttribute(1).strideInBytes).toEqual(expectedStride); expect(va.getAttribute(0).vertexBuffer).toBe(va.getAttribute(1).vertexBuffer); }); it('sorts interleaved attributes from large to small components', function() { var geometry = new Geometry({ attributes : { bytes : new GeometryAttribute({ componentDatatype : ComponentDatatype.BYTE, componentsPerAttribute : 1, values : [0] }), shorts : new GeometryAttribute({ componentDatatype : ComponentDatatype.SHORT, componentsPerAttribute : 1, values : [1] }), floats : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [2] }) }, primitiveType : PrimitiveType.POINTS }); var attributeLocations = GeometryPipeline.createAttributeLocations(geometry); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : attributeLocations, interleave : true }); expect(va.numberOfAttributes).toEqual(3); var vertexBuffer = va.getAttribute(0).vertexBuffer; expect(vertexBuffer).toBe(va.getAttribute(1).vertexBuffer); expect(vertexBuffer).toBe(va.getAttribute(2).vertexBuffer); expect(vertexBuffer.sizeInBytes).toEqual(8); // Includes 1 byte per-vertex padding // Validate via rendering var vs = 'attribute float bytes; ' + 'attribute float shorts; ' + 'attribute float floats; ' + 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_PointSize = 1.0; ' + ' gl_Position = vec4(0.0, 0.0, 0.0, 1.0); ' + ' fsColor = vec4((bytes == 0.0) && (shorts == 1.0) && (floats == 2.0)); ' + '}'; var fs = 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_FragColor = fsColor; ' + '}'; sp = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); var command = new DrawCommand({ primitiveType : PrimitiveType.POINTS, shaderProgram : sp, vertexArray : va }); command.execute(context); expect(context).toReadPixels([255, 255, 255, 255]); }); it('sorts interleaved attributes from large to small components (2)', function() { var geometry = new Geometry({ attributes : { color : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 4, normalize : true, values : [255, 0, 0, 255, 0, 255, 0, 255] }), position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }) }, primitiveType : PrimitiveType.POINTS }); var attributeLocations = GeometryPipeline.createAttributeLocations(geometry); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : attributeLocations, interleave : true }); expect(va.getAttribute(0).vertexBuffer.sizeInBytes).toEqual(32); // No per-vertex padding needed // Validate via rendering var vs = 'attribute vec3 position; ' + 'attribute vec4 color; ' + 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_PointSize = 1.0; ' + ' gl_Position = vec4(position, 1.0); ' + ' fsColor = color; ' + '}'; var fs = 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_FragColor = fsColor; ' + '}'; sp = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); var command = new DrawCommand({ primitiveType : PrimitiveType.POINTS, shaderProgram : sp, vertexArray : va, offset : 0, count : 1 }); command.execute(context); expect(context).toReadPixels([255, 0, 0, 255]); command = new DrawCommand({ primitiveType : PrimitiveType.POINTS, shaderProgram : sp, vertexArray : va, offset : 1, count : 1 }); command.execute(context); expect(context).toReadPixels([0, 255, 0, 255]); }); it('sorts interleaved attributes from large to small components (3)', function() { var geometry = new Geometry({ attributes : { unsignedByteAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 2, values : [1, 2] }), unsignedShortAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_SHORT, componentsPerAttribute : 1, values : [3] }), byteAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.BYTE, componentsPerAttribute : 1, values : [4] }), shortAttribute : new GeometryAttribute({ componentDatatype : ComponentDatatype.SHORT, componentsPerAttribute : 1, values : [5] }) }, primitiveType : PrimitiveType.POINTS }); var attributeLocations = GeometryPipeline.createAttributeLocations(geometry); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : attributeLocations, interleave : true }); expect(va.numberOfAttributes).toEqual(4); expect(va.getAttribute(0).vertexBuffer.sizeInBytes).toEqual(8); // Includes 1 byte per-vertex padding // Validate via rendering var vs = 'attribute vec2 unsignedByteAttribute; ' + 'attribute float unsignedShortAttribute; ' + 'attribute float byteAttribute; ' + 'attribute float shortAttribute; ' + 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_PointSize = 1.0; ' + ' gl_Position = vec4(0.0, 0.0, 0.0, 1.0); ' + ' fsColor = vec4((unsignedByteAttribute.x == 1.0) && (unsignedByteAttribute.y == 2.0) && (unsignedShortAttribute == 3.0) && (byteAttribute == 4.0) && (shortAttribute == 5.0)); ' + '}'; var fs = 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_FragColor = fsColor; ' + '}'; sp = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); var command = new DrawCommand({ primitiveType : PrimitiveType.POINTS, shaderProgram : sp, vertexArray : va }); command.execute(context); expect(context).toReadPixels([255, 255, 255, 255]); }); it('creates a custom interleaved vertex', function() { var geometry = new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }), color : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 3, normalize : true, values : [255, 0, 0, 0, 255, 0] }), normal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : [1.0, 0.0, 0.0, 0.0, 1.0, 0.0] }), temperature : new GeometryAttribute({ componentDatatype : ComponentDatatype.UNSIGNED_SHORT, componentsPerAttribute : 1, values : [75, 100] }) }, primitiveType : PrimitiveType.POINTS }); var attributeLocations = GeometryPipeline.createAttributeLocations(geometry); var va = VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : attributeLocations, interleave : true }); expect(va.getAttribute(0).vertexBuffer.sizeInBytes).toEqual(2 * 32); // Includes 3 byte per-vertex padding // Validate via rendering var vs = 'attribute vec3 position; ' + 'attribute vec3 color; ' + 'attribute vec3 normal; ' + 'attribute float temperature; ' + 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_PointSize = 1.0; ' + ' gl_Position = vec4(position, 1.0); ' + ' if ((normal == vec3(1.0, 0.0, 0.0)) && (temperature == 75.0)) { ' + ' fsColor = vec4(color, 1.0); ' + ' } ' + ' else {' + ' fsColor = vec4(1.0); ' + ' }' + '}'; var fs = 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_FragColor = fsColor; ' + '}'; sp = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs, fragmentShaderSource : fs, attributeLocations : attributeLocations }); ClearCommand.ALL.execute(context); expect(context).toReadPixels([0, 0, 0, 255]); var command = new DrawCommand({ primitiveType : PrimitiveType.POINTS, shaderProgram : sp, vertexArray : va, offset : 0, count : 1 }); command.execute(context); expect(context).toReadPixels([255, 0, 0, 255]); var vs2 = 'attribute vec3 position; ' + 'attribute vec3 color; ' + 'attribute vec3 normal; ' + 'attribute float temperature; ' + 'varying vec4 fsColor; ' + 'void main() { ' + ' gl_PointSize = 1.0; ' + ' gl_Position = vec4(position, 1.0); ' + ' if ((normal == vec3(0.0, 1.0, 0.0)) && (temperature == 100.0)) { ' + ' fsColor = vec4(color, 1.0); ' + ' } ' + ' else {' + ' fsColor = vec4(1.0); ' + ' }' + '}'; sp = sp.destroy(); sp = ShaderProgram.fromCache({ context : context, vertexShaderSource : vs2, fragmentShaderSource : fs, attributeLocations : attributeLocations }); command = new DrawCommand({ primitiveType : PrimitiveType.POINTS, shaderProgram : sp, vertexArray : va, offset : 1, count : 1 }); command.execute(context); expect(context).toReadPixels([0, 255, 0, 255]); }); it('creates an index buffer', function() { var geometry = new Geometry({ attributes : {}, indices : [0], primitiveType : PrimitiveType.POINTS }); var va = VertexArray.fromGeometry({ context : context, geometry : geometry }); expect(va.numberOfAttributes).toEqual(0); expect(va.indexBuffer).toBeDefined(); expect(va.indexBuffer.usage).toEqual(BufferUsage.DYNAMIC_DRAW); // Default expect(va.indexBuffer.indexDatatype).toEqual(IndexDatatype.UNSIGNED_SHORT); expect(va.indexBuffer.numberOfIndices).toEqual(geometry.indices.length); }); it('throws with different number of interleaved attributes', function() { var geometry = new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [0.0] }), normal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [1.0, 2.0] }) }, primitiveType : PrimitiveType.POINTS }); expect(function() { return VertexArray.fromGeometry({ context : context, geometry : geometry, interleave : true }); }).toThrowRuntimeError(); }); it('throws with duplicate indices', function() { var geometry = new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [0.0] }), normal : new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 1, values : [1.0] }) }, primitiveType : PrimitiveType.POINTS }); expect(function() { return VertexArray.fromGeometry({ context : context, geometry : geometry, attributeLocations : { position : 0, normal : 0 } }); }).toThrowDeveloperError(); }); }, 'WebGL');