import { ComponentDatatype } from "../../Source/Cesium.js"; import { Geometry } from "../../Source/Cesium.js"; import { GeometryAttribute } from "../../Source/Cesium.js"; import { GeometryPipeline } from "../../Source/Cesium.js"; import { IndexDatatype } from "../../Source/Cesium.js"; import { PrimitiveType } from "../../Source/Cesium.js"; import { BufferUsage } from "../../Source/Cesium.js"; import { ClearCommand } from "../../Source/Cesium.js"; import { DrawCommand } from "../../Source/Cesium.js"; import { ShaderProgram } from "../../Source/Cesium.js"; import { VertexArray } from "../../Source/Cesium.js"; import createContext from "../createContext.js"; describe( "Renderer/VertexArrayFactory", function () { 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" );