import { ComponentDatatype } from "../../Source/Cesium.js"; import { PrimitiveType } from "../../Source/Cesium.js"; import { Buffer } from "../../Source/Cesium.js"; import { BufferUsage } 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/VertexArray", function () { var context; beforeAll(function () { context = createContext(); }); afterAll(function () { context.destroyForSpecs(); }); it("binds", function () { var positionBuffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { index: 0, enabled: true, vertexBuffer: positionBuffer, componentsPerAttribute: 3, componentDatatype: ComponentDatatype.FLOAT, normalize: false, offsetInBytes: 0, strideInBytes: 0, instanceDivisor: 0, // tightly packed }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); va._bind(); va._unBind(); va = va.destroy(); }); it("binds with default values", function () { var positionBuffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: positionBuffer, componentsPerAttribute: 3, }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); expect(va.numberOfAttributes).toEqual(1); expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).enabled).toEqual(true); expect(va.getAttribute(0).vertexBuffer).toEqual(positionBuffer); expect(va.getAttribute(0).componentsPerAttribute).toEqual(3); expect(va.getAttribute(0).componentDatatype).toEqual( ComponentDatatype.FLOAT ); expect(va.getAttribute(0).normalize).toEqual(false); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(0); expect(va.getAttribute(0).instanceDivisor).toEqual(0); va._bind(); va._unBind(); va = va.destroy(); }); it("binds with multiple buffers", function () { var attributeSize = 3 * Float32Array.BYTES_PER_ELEMENT; var positionBuffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: attributeSize, usage: BufferUsage.STATIC_DRAW, }); var normalBuffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: attributeSize, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { index: 0, vertexBuffer: positionBuffer, componentsPerAttribute: 3, componentDatatype: ComponentDatatype.FLOAT, }, { index: 1, vertexBuffer: normalBuffer, componentsPerAttribute: 3, componentDatatype: ComponentDatatype.FLOAT, }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); expect(va.numberOfAttributes).toEqual(2); va._bind(); va._unBind(); va = va.destroy(); }); it("binds with interleaved buffer", function () { var attributeSize = 3 * Float32Array.BYTES_PER_ELEMENT; var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: attributeSize, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, componentsPerAttribute: 3, componentDatatype: ComponentDatatype.FLOAT, offsetInBytes: 0, strideInBytes: 2 * attributeSize, }, { vertexBuffer: buffer, componentsPerAttribute: 3, componentDatatype: ComponentDatatype.FLOAT, normalize: true, offsetInBytes: attributeSize, strideInBytes: 2 * attributeSize, }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); expect(va.numberOfAttributes).toEqual(2); va._bind(); va._unBind(); va = va.destroy(); }); it("adds attributes", function () { var positionBuffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var va = new VertexArray({ context: context, attributes: [ { vertexBuffer: positionBuffer, componentsPerAttribute: 3, }, ], }); expect(va.numberOfAttributes).toEqual(1); expect(va.getAttribute(0).index).toEqual(0); expect(va.getAttribute(0).enabled).toEqual(true); expect(va.getAttribute(0).vertexBuffer).toEqual(positionBuffer); expect(va.getAttribute(0).componentsPerAttribute).toEqual(3); expect(va.getAttribute(0).componentDatatype).toEqual( ComponentDatatype.FLOAT ); expect(va.getAttribute(0).normalize).toEqual(false); expect(va.getAttribute(0).offsetInBytes).toEqual(0); expect(va.getAttribute(0).strideInBytes).toEqual(0); expect(va.getAttribute(0).instanceDivisor).toEqual(0); va = va.destroy(); }); it("modifies attributes", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 6, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, componentsPerAttribute: 3, }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); expect(va.numberOfAttributes).toEqual(1); expect(va.getAttribute(0).enabled).toEqual(true); va.getAttribute(0).enabled = false; expect(va.getAttribute(0).enabled).toEqual(false); va._bind(); va._unBind(); va = va.destroy(); }); // The following specs test draw calls that pull from a constant attribute. // Due to what I believe is a range checking bug in Firefox (Section 6.4 of // the WebGL spec), an attribute backed by a buffer must also be bound, // otherwise drawArrays unjustly reports an INVALID_OPERATION, hence the // firefoxWorkaround attribute below. In practice, we will always have // an attribute backed by a buffer anyway. it("renders with a one-component constant value", function () { var vs = "attribute float firefoxWorkaround;" + "attribute float attr;" + "varying vec4 v_color;" + "void main() { " + " v_color = vec4(attr == 0.5) + vec4(firefoxWorkaround);" + " gl_PointSize = 1.0;" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);" + "}"; var fs = "varying vec4 v_color;" + "void main() { gl_FragColor = v_color; }"; var sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { firefoxWorkaround: 0, attr: 1, }, }); var va = new VertexArray({ context: context, attributes: [ { vertexBuffer: Buffer.createVertexBuffer({ context: context, sizeInBytes: Float32Array.BYTES_PER_ELEMENT, usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 1, }, { value: [0.5], }, ], }); var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, count: 1, }); command.execute(context); expect(context).toReadPixels([255, 255, 255, 255]); sp = sp.destroy(); va = va.destroy(); }); it("renders with a two-component constant value", function () { var vs = "attribute float firefoxWorkaround;" + "attribute vec2 attr;" + "varying vec4 v_color;" + "void main() { " + " v_color = vec4(attr == vec2(0.25, 0.75)) + vec4(firefoxWorkaround);" + " gl_PointSize = 1.0;" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);" + "}"; var fs = "varying vec4 v_color;" + "void main() { gl_FragColor = v_color; }"; var sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { firefoxWorkaround: 0, attr: 1, }, }); var va = new VertexArray({ context: context, attributes: [ { vertexBuffer: Buffer.createVertexBuffer({ context: context, sizeInBytes: Float32Array.BYTES_PER_ELEMENT, usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 1, }, { value: [0.25, 0.75], }, ], }); var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, count: 1, }); command.execute(context); expect(context).toReadPixels([255, 255, 255, 255]); sp = sp.destroy(); va = va.destroy(); }); it("renders with a three-component constant value", function () { var vs = "attribute float firefoxWorkaround;" + "attribute vec3 attr;" + "varying vec4 v_color;" + "void main() { " + " v_color = vec4(attr == vec3(0.25, 0.5, 0.75)) + vec4(firefoxWorkaround);" + " gl_PointSize = 1.0;" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);" + "}"; var fs = "varying vec4 v_color;" + "void main() { gl_FragColor = v_color; }"; var sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { firefoxWorkaround: 0, attr: 1, }, }); var va = new VertexArray({ context: context, attributes: [ { vertexBuffer: Buffer.createVertexBuffer({ context: context, sizeInBytes: Float32Array.BYTES_PER_ELEMENT, usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 1, }, { value: [0.25, 0.5, 0.75], }, ], }); var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, count: 1, }); command.execute(context); expect(context).toReadPixels([255, 255, 255, 255]); sp = sp.destroy(); va = va.destroy(); }); it("renders with a four-component constant value", function () { var vs = "attribute float firefoxWorkaround;" + "attribute vec4 attr;" + "varying vec4 v_color;" + "void main() { " + " v_color = vec4(attr == vec4(0.2, 0.4, 0.6, 0.8)) + vec4(firefoxWorkaround);" + " gl_PointSize = 1.0;" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);" + "}"; var fs = "varying vec4 v_color;" + "void main() { gl_FragColor = v_color; }"; var sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { firefoxWorkaround: 0, attr: 1, }, }); var va = new VertexArray({ context: context, attributes: [ { vertexBuffer: Buffer.createVertexBuffer({ context: context, sizeInBytes: Float32Array.BYTES_PER_ELEMENT, usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 1, }, { value: [0.2, 0.4, 0.6, 0.8], }, ], }); var command = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: va, count: 1, }); command.execute(context); expect(context).toReadPixels([255, 255, 255, 255]); sp = sp.destroy(); va = va.destroy(); }); it("renders two vertex arrays with constant values", function () { var vs = "attribute float firefoxWorkaround;" + "attribute vec4 attr;" + "varying vec4 v_color;" + "void main() { " + " v_color = attr + vec4(firefoxWorkaround);" + " gl_PointSize = 1.0;" + " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);" + "}"; var fs = "varying vec4 v_color;" + "void main() { gl_FragColor = v_color; }"; var sp = ShaderProgram.fromCache({ context: context, vertexShaderSource: vs, fragmentShaderSource: fs, attributeLocations: { firefoxWorkaround: 0, attr: 1, }, }); var vertexBuffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: Float32Array.BYTES_PER_ELEMENT, usage: BufferUsage.STATIC_DRAW, }); var vaRed = new VertexArray({ context: context, attributes: [ { vertexBuffer: vertexBuffer, componentsPerAttribute: 1, }, { value: [1, 0, 0, 1], }, ], }); var vaGreen = new VertexArray({ context: context, attributes: [ { vertexBuffer: vertexBuffer, componentsPerAttribute: 1, }, { value: [0, 1, 0, 1], }, ], }); var commandRed = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: vaRed, count: 1, }); var commandGreen = new DrawCommand({ primitiveType: PrimitiveType.POINTS, shaderProgram: sp, vertexArray: vaGreen, count: 1, }); commandRed.execute(context); expect(context).toReadPixels([255, 0, 0, 255]); commandGreen.execute(context); expect(context).toReadPixels([0, 255, 0, 255]); sp = sp.destroy(); vaRed = vaRed.destroy(); vaGreen = vaGreen.destroy(); }); it("destroys", function () { var va = new VertexArray({ context: context, attributes: [ { vertexBuffer: Buffer.createVertexBuffer({ context: context, sizeInBytes: new Float32Array([0, 0, 0, 1]).byteLength, usage: BufferUsage.STATIC_DRAW, }), componentsPerAttribute: 4, }, ], }); expect(va.isDestroyed()).toEqual(false); va.destroy(); expect(va.isDestroyed()).toEqual(true); }); it("fails to create (missing vertexBuffer and value)", function () { var attributes = [ { componentsPerAttribute: 3, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (provides both vertexBuffer and value)", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, value: [1, 2, 3], componentsPerAttribute: 3, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create with duplicate indices", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 1, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { index: 1, vertexBuffer: buffer, componentsPerAttribute: 1, }, { index: 1, vertexBuffer: buffer, componentsPerAttribute: 1, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (componentsPerAttribute missing)", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (componentsPerAttribute < 1)", function () { var attributes = [ { componentsPerAttribute: 0, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (componentsPerAttribute > 4)", function () { var attributes = [ { componentsPerAttribute: 5, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (value.length < 1)", function () { var attributes = [ { value: [], }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (value.length > 4)", function () { var attributes = [ { value: [1.0, 2.0, 3.0, 4.0, 5.0], }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (componentDatatype)", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, componentsPerAttribute: 3, componentDatatype: "invalid component datatype", }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to create (strideInBytes)", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, componentsPerAttribute: 3, strideInBytes: 256, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("fails to get attribute", function () { var attributes = [ { value: [0.0, 0.0, 0.0], componentsPerAttribute: 3, }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); expect(function () { return va.getAttribute(); }).toThrowDeveloperError(); }); it("fails to destroy", function () { var attributes = [ { value: [0.0, 0.0, 0.0], componentsPerAttribute: 3, }, ]; var va = new VertexArray({ context: context, attributes: attributes, }); va.destroy(); expect(function () { va.destroy(); }).toThrowDeveloperError(); }); it("throws when there is no context", function () { expect(function () { return new VertexArray(); }).toThrowDeveloperError(); }); it("throws if instanceDivisor is less than zero", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { vertexBuffer: buffer, componentsPerAttribute: 3, instanceDivisor: -1, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); // Direct3D 9 requires vertex attribute zero to not be instanced. While ANGLE can work around this, it is best // to follow this convention. This test also guarantees that not all vertex attributes are instanced. it("throws if vertex attribute zero is instanced", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { index: 0, vertexBuffer: buffer, componentsPerAttribute: 3, instanceDivisor: 1, }, { index: 1, vertexBuffer: buffer, componentsPerAttribute: 3, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("throws if an attribute has an instanceDivisor and is not backed by a buffer", function () { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { index: 0, vertexBuffer: buffer, componentsPerAttribute: 3, }, { index: 1, value: [0.0, 0.0, 1.0], componentsPerAttribute: 3, instanceDivisor: 1, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); }); it("throws when instanceDivisor is greater than zero and the instanced arrays extension is not supported.", function () { if (!context.instancedArrays) { var buffer = Buffer.createVertexBuffer({ context: context, sizeInBytes: 3, usage: BufferUsage.STATIC_DRAW, }); var attributes = [ { index: 0, vertexBuffer: buffer, componentsPerAttribute: 3, }, { index: 1, vertexBuffer: buffer, componentsPerAttribute: 3, instanceDivisor: 1, }, ]; expect(function () { return new VertexArray({ context: context, attributes: attributes, }); }).toThrowDeveloperError(); } }); }, "WebGL" );