import { BoundingRectangle } from "../../Source/Cesium.js";
import { Color } from "../../Source/Cesium.js";
import { ComponentDatatype } from "../../Source/Cesium.js";
import { IndexDatatype } from "../../Source/Cesium.js";
import { PrimitiveType } from "../../Source/Cesium.js";
import { WebGLConstants } from "../../Source/Cesium.js";
import { WindingOrder } from "../../Source/Cesium.js";
import { Buffer } from "../../Source/Cesium.js";
import { BufferUsage } from "../../Source/Cesium.js";
import { ClearCommand } from "../../Source/Cesium.js";
import { ContextLimits } from "../../Source/Cesium.js";
import { DrawCommand } from "../../Source/Cesium.js";
import { RenderState } from "../../Source/Cesium.js";
import { ShaderProgram } from "../../Source/Cesium.js";
import { VertexArray } from "../../Source/Cesium.js";
import { BlendingState } from "../../Source/Cesium.js";
import createContext from "../createContext.js";

describe(
  "Renderer/Draw",
  function () {
    var context;
    var sp;
    var va;

    beforeAll(function () {
      context = createContext();
    });

    afterAll(function () {
      context.destroyForSpecs();
    });

    afterEach(function () {
      sp = sp && sp.destroy();
      va = va && va.destroy();
    });

    it("draws a white point", function () {
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      expect({
        context: context,
        fragmentShader: fs,
      }).contextToRender();
    });

    it("draws a white point with an index buffer", function () {
      // Use separate context to work around IE 11.0.9 bug
      var context = createContext();

      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      // Two indices instead of one is a workaround for NVIDIA:
      //   http://www.khronos.org/message_boards/viewtopic.php?f=44&t=3719
      var indexBuffer = Buffer.createIndexBuffer({
        context: context,
        typedArray: new Uint16Array([0, 0]),
        usage: BufferUsage.STATIC_DRAW,
        indexDatatype: IndexDatatype.UNSIGNED_SHORT,
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
        indexBuffer: indexBuffer,
      });

      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]);

      sp = sp.destroy();
      va = va.destroy();
      context.destroyForSpecs();
    });

    it("draws a red point with two vertex buffers", function () {
      var vs =
        "attribute vec4 position;" +
        "attribute mediump float intensity;" +
        "varying mediump float fs_intensity;" +
        "void main() {" +
        "  gl_PointSize = 1.0; " +
        "  gl_Position = position;" +
        "  fs_intensity = intensity;" +
        "}";
      var fs =
        "varying mediump float fs_intensity; void main() { gl_FragColor = vec4(fs_intensity, 0.0, 0.0, 1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
          intensity: 1,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
          {
            index: 1,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 1,
          },
        ],
      });

      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, 0, 0, 255]);
    });

    it("draws a red point with one interleaved vertex buffers", function () {
      var vs =
        "attribute vec4 position;" +
        "attribute mediump float intensity;" +
        "varying mediump float fs_intensity;" +
        "void main() {" +
        "  gl_PointSize = 1.0; " +
        "  gl_Position = position;" +
        "  fs_intensity = intensity;" +
        "}";
      var fs =
        "varying mediump float fs_intensity; void main() { gl_FragColor = vec4(fs_intensity, 0.0, 0.0, 1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
          intensity: 1,
        },
      });

      var stride = 5 * Float32Array.BYTES_PER_ELEMENT;
      var vertexBuffer = Buffer.createVertexBuffer({
        context: context,
        typedArray: new Float32Array([0, 0, 0, 1, 1]),
        usage: BufferUsage.STATIC_DRAW,
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: vertexBuffer,
            componentsPerAttribute: 4,
            offsetInBytes: 0,
            strideInBytes: stride,
          },
          {
            index: 1,
            vertexBuffer: vertexBuffer,
            componentsPerAttribute: 1,
            offsetInBytes: 4 * Float32Array.BYTES_PER_ELEMENT,
            strideInBytes: stride,
          },
        ],
      });

      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, 0, 0, 255]);
    });

    it("draws with scissor test", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      // 1 of 3:  Clear to black
      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 2 of 3:  Render point - fails scissor test
      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          scissorTest: {
            enabled: true,
            rectangle: new BoundingRectangle(1, 1, 0, 0),
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 3 of 3:  Render point - passes scissor test
      command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          scissorTest: {
            enabled: true,
            rectangle: new BoundingRectangle(0, 0, 1, 1),
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with color mask", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      // 1 of 3:  Clear to black
      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 2 of 3:  Render point - blue color mask
      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          colorMask: {
            red: true,
            green: false,
            blue: false,
            alpha: false,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 0, 0, 255]);

      // 3 of 3:  Render point - red color mask (blue channel not touched)
      command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          colorMask: {
            red: false,
            green: false,
            blue: true,
            alpha: false,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 0, 255, 255]);
    });

    it("draws with additive blending", function () {
      var cxt = createContext({
        webgl: {
          alpha: true,
        },
      });

      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(0.5); }";
      var sp = ShaderProgram.fromCache({
        context: cxt,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      var va = new VertexArray({
        context: cxt,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: cxt,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      // 1 of 3:  Clear to black
      ClearCommand.ALL.execute(cxt);
      expect(cxt).toReadPixels([0, 0, 0, 0]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          blending: {
            enabled: true,
            equationRgb: WebGLConstants.FUNC_ADD, // Optional, default
            equationAlpha: WebGLConstants.FUNC_ADD, // Optional, default
            functionSourceRgb: WebGLConstants.ONE, // Optional, default
            functionSourceAlpha: WebGLConstants.ONE, // Optional, default
            functionDestinationRgb: WebGLConstants.ONE,
            functionDestinationAlpha: WebGLConstants.ONE,
          },
        }),
      });

      // 2 of 3:  Blend:  0 + 0.5
      command.execute(cxt);
      expect({
        context: cxt,
        epsilon: 1,
      }).toReadPixels([127, 127, 127, 127]);

      // 3 of 3:  Blend:  0.5 + 0.5
      command.execute(cxt);
      expect({
        context: cxt,
        epsilon: 1,
      }).toReadPixels([254, 254, 254, 254]);

      sp.destroy();
      va.destroy();
      cxt.destroyForSpecs();
    });

    it("draws with alpha blending", function () {
      var cxt = createContext({
        webgl: {
          alpha: true,
        },
      });

      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 0.5); }";
      var sp = ShaderProgram.fromCache({
        context: cxt,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      var va = new VertexArray({
        context: cxt,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: cxt,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      // 1 of 3:  Clear to black
      ClearCommand.ALL.execute(cxt);
      expect(cxt).toReadPixels([0, 0, 0, 0]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          blending: {
            enabled: true,
            equationRgb: WebGLConstants.FUNC_ADD,
            equationAlpha: WebGLConstants.FUNC_SUBTRACT, // does not actually matter
            functionSourceRgb: WebGLConstants.SRC_ALPHA,
            functionSourceAlpha: WebGLConstants.ONE, // Don't blend alpha
            functionDestinationRgb: WebGLConstants.ONE_MINUS_SRC_ALPHA,
            functionDestinationAlpha: WebGLConstants.ZERO,
          },
        }),
      });

      // 2 of 3:  Blend:  RGB:  (255 * 0.5) + (0 * 0.5), Alpha: 0.5 + 0
      command.execute(cxt);
      expect({
        context: cxt,
        epsilon: 1,
      }).toReadPixels([127, 127, 127, 127]);

      // 3 of 3:  Blend:  RGB:  (255 * 0.5) + (127 * 0.5), Alpha: 0.5 + 0
      command.execute(cxt);
      expect({
        context: cxt,
        epsilon: 2,
      }).toReadPixels([191, 191, 191, 127]);

      sp.destroy();
      va.destroy();
      cxt.destroyForSpecs();
    });

    it("draws with blend color", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          blending: {
            enabled: true,
            color: {
              red: 0.5,
              green: 0.5,
              blue: 0.5,
              alpha: 0.5,
            },
            equationRgb: WebGLConstants.FUNC_SUBTRACT,
            equationAlpha: WebGLConstants.FUNC_SUBTRACT,
            functionSourceRgb: WebGLConstants.CONSTANT_COLOR,
            functionSourceAlpha: WebGLConstants.ONE,
            functionDestinationRgb: WebGLConstants.ZERO,
            functionDestinationAlpha: WebGLConstants.ZERO,
          },
        }),
      });

      // 2 of 3:  Blend:  RGB:  255 - 127, Alpha: 255 - (255 - 255)
      //   Epsilon of 1 because ANGLE gives 127 and desktop GL gives 128.
      command.execute(context);
      expect({
        context: context,
        epsilon: 1,
      }).toReadPixels([128, 128, 128, 255]);
    });

    it("draws with culling", function () {
      var vs =
        "attribute vec4 position; void main() { gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([
                -1000,
                -1000,
                0,
                1,
                1000,
                -1000,
                0,
                1,
                -1000,
                1000,
                0,
                1,
                1000,
                1000,
                0,
                1,
              ]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      // 1 of 3:  Clear to black
      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 2 of 3:  Cull front faces - nothing is drawn
      var command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          cull: {
            enabled: true,
            face: WebGLConstants.FRONT,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 3 of 3:  Cull back faces - nothing is culled
      command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          cull: {
            enabled: true,
            face: WebGLConstants.BACK,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with front face winding order", function () {
      var vs =
        "attribute vec4 position; void main() { gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([
                -1000,
                -1000,
                0,
                1,
                1000,
                -1000,
                0,
                1,
                -1000,
                1000,
                0,
                1,
                1000,
                1000,
                0,
                1,
              ]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      // 1 of 3:  Clear to black
      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 2 of 3:  Cull back faces with opposite winding order - nothing is drawn
      var command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          frontFace: WindingOrder.CLOCKWISE,
          cull: {
            enabled: true,
            face: WebGLConstants.BACK,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 3 of 3:  Cull back faces with correct winding order - nothing is culled
      command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          frontFace: WindingOrder.COUNTER_CLOCKWISE,
          cull: {
            enabled: true,
            face: WebGLConstants.BACK,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with the depth test", function () {
      var vs =
        "attribute vec4 position; void main() { gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([
                -1000,
                -1000,
                0,
                1,
                1000,
                -1000,
                0,
                1,
                -1000,
                1000,
                0,
                1,
                1000,
                1000,
                0,
                1,
              ]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      var command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          depthTest: {
            enabled: true,
            func: WebGLConstants.LEQUAL,
          },
        }),
      });

      // 1 of 2.  Triangle fan passes the depth test.

      var clearCommand = new ClearCommand({
        color: new Color(0.0, 0.0, 0.0, 0.0),
        depth: 1.0,
      });
      clearCommand.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);

      // 2 of 2.  Triangle fan fails the depth test.
      clearCommand.color = new Color(0.0, 0.0, 0.0, 0.0);
      clearCommand.depth = 0.0;
      clearCommand.execute(context);

      expect(context).toReadPixels([0, 0, 0, 255]);

      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);
    });

    it("draws with depth range", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs =
        "void main() { gl_FragColor = vec4(gl_DepthRange.near, gl_DepthRange.far, 0.0, 1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          depthRange: {
            near: 0.25,
            far: 0.75,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([64, 191, 0, 255]);
    });

    it("draws with line width", function () {
      var vs =
        "attribute vec4 position; void main() { gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([
                -1000,
                -1000,
                0,
                1,
                1000,
                1000,
                0,
                1,
              ]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.LINES,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          lineWidth: ContextLimits.maximumAliasedLineWidth,
          // May only be 1.
        }),
      });
      command.execute(context);

      // I believe different GL implementations are allowed to AA
      // in different ways (or at least that is what we see in practice),
      // so verify it at least rendered something.
      expect(context).notToReadPixels([0, 0, 0, 255]);
    });

    it("draws with polygon offset", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          polygonOffset: {
            enabled: true,
            factor: 1,
            units: 1,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with sample coverage", function () {
      if (!context.antialias) {
        // Sample coverage requires antialiasing.
        return;
      }

      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          sampleCoverage: {
            enabled: true,
            value: 0,
            invert: false,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          sampleCoverage: {
            enabled: false,
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with stencil test (front)", function () {
      if (context.stencilBits === 0) {
        return;
      }

      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([
                -1000,
                -1000,
                0,
                1,
                1000,
                -1000,
                0,
                1,
                -1000,
                1000,
                0,
                1,
                1000,
                1000,
                0,
                1,
              ]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      var rs = RenderState.fromCache({
        stencilTest: {
          enabled: true,
          frontFunction: WebGLConstants.EQUAL,
          reference: 1,
          mask: 1,
        },
      });

      // 1 of 4.  Clear, including stencil
      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 2 of 4.  Render where stencil is set - nothing is drawn
      var command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: rs,
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 3 of 4.  Render to stencil only, increment
      command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          colorMask: {
            red: false,
            green: false,
            blue: false,
            alpha: false,
          },
          stencilTest: {
            enabled: true,
            frontOperation: {
              zPass: WebGLConstants.INCR,
            },
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 4 of 4.  Render where stencil is set
      command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: rs,
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with stencil test (back)", function () {
      if (context.stencilBits === 0) {
        return;
      }

      var vs =
        "attribute vec4 position; void main() { gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([
                -1000,
                -1000,
                0,
                1,
                1000,
                -1000,
                0,
                1,
                -1000,
                1000,
                0,
                1,
                1000,
                1000,
                0,
                1,
              ]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      var rs = RenderState.fromCache({
        frontFace: WindingOrder.CLOCKWISE,
        stencilTest: {
          enabled: true,
          backFunction: WebGLConstants.NOTEQUAL,
          reference: 0,
        },
      });

      // 1 of 4.  Clear, including stencil
      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 2 of 4.  Render where stencil is set - nothing is drawn
      var command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: rs,
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 3 of 4.  Render to stencil only, increment
      command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: RenderState.fromCache({
          frontFace: WindingOrder.CLOCKWISE,
          colorMask: {
            red: false,
            green: false,
            blue: false,
            alpha: false,
          },
          stencilTest: {
            enabled: true,
            backOperation: {
              zPass: WebGLConstants.INVERT,
            },
          },
        }),
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // 4 of 4.  Render where stencil is set
      command = new DrawCommand({
        primitiveType: PrimitiveType.TRIANGLE_STRIP,
        shaderProgram: sp,
        vertexArray: va,
        renderState: rs,
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws with an offset and count", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, -1, 0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      ClearCommand.ALL.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      // The first point in the vertex buffer does not generate any pixels
      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        offset: 0,
        count: 1,
        shaderProgram: sp,
        vertexArray: va,
      });
      command.execute(context);
      expect(context).toReadPixels([0, 0, 0, 255]);

      command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        offset: 1,
        count: 1,
        shaderProgram: sp,
        vertexArray: va,
      });
      command.execute(context);
      expect(context).toReadPixels([255, 255, 255, 255]);
    });

    it("draws two instances of a point with different per-instance colors", function () {
      if (context.instancedArrays) {
        var vs =
          "attribute vec4 position;" +
          "attribute vec4 color;" +
          "varying vec4 v_color;" +
          "void main() {" +
          "  gl_PointSize = 1.0; " +
          "  gl_Position = position;" +
          "  v_color = color;" +
          "}";
        var fs =
          "varying vec4 v_color; void main() { gl_FragColor = v_color; }";
        sp = ShaderProgram.fromCache({
          context: context,
          vertexShaderSource: vs,
          fragmentShaderSource: fs,
          attributeLocations: {
            position: 0,
            color: 1,
          },
        });

        va = new VertexArray({
          context: context,
          attributes: [
            {
              index: 0,
              vertexBuffer: Buffer.createVertexBuffer({
                context: context,
                typedArray: new Float32Array([0, 0, 0, 1]),
                usage: BufferUsage.STATIC_DRAW,
              }),
              componentsPerAttribute: 4,
            },
            {
              index: 1,
              vertexBuffer: Buffer.createVertexBuffer({
                context: context,
                typedArray: new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255]),
                usage: BufferUsage.STATIC_DRAW,
              }),
              componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
              componentsPerAttribute: 4,
              normalize: true,
              instanceDivisor: 1,
            },
          ],
        });

        ClearCommand.ALL.execute(context);
        expect(context).toReadPixels([0, 0, 0, 255]);

        var command = new DrawCommand({
          primitiveType: PrimitiveType.POINTS,
          shaderProgram: sp,
          vertexArray: va,
          instanceCount: 2,
          renderState: RenderState.fromCache({
            blending: BlendingState.ADDITIVE_BLEND,
          }),
        });
        command.execute(context);
        expect(context).toReadPixels([255, 255, 0, 255]);
      }
    });

    it("fails to draw (missing command)", function () {
      expect(function () {
        context.draw();
      }).toThrowDeveloperError();
    });

    it("fails to draw (missing shaderProgram)", function () {
      expect(function () {
        context.draw({
          primitiveType: PrimitiveType.POINTS,
        });
      }).toThrowDeveloperError();
    });

    it("fails to draw (missing primitiveType)", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
      });

      expect(function () {
        context.draw({
          shaderProgram: sp,
        });
      }).toThrowDeveloperError();
    });

    it("fails to draw (primitiveType)", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
      });

      expect(function () {
        context.draw({
          primitiveType: "invalid value",
          shaderProgram: sp,
        });
      }).toThrowDeveloperError();
    });

    it("fails to draw (missing vertexArray)", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
      });

      expect(function () {
        context.draw({
          primitiveType: PrimitiveType.POINTS,
          shaderProgram: sp,
        });
      }).toThrowDeveloperError();
    });

    it("fails to draw (negative offset)", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
      });

      expect(function () {
        context.draw({
          primitiveType: PrimitiveType.POINTS,
          shaderProgram: sp,
          vertexArray: new VertexArray({
            context: context,
          }),
          offset: -1,
          count: 1,
        });
      }).toThrowDeveloperError();
    });

    it("throws if instanceCount is less than one", function () {
      var vs =
        "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
      var fs = "void main() { gl_FragColor = vec4(1.0); }";
      sp = ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: {
          position: 0,
        },
      });

      va = new VertexArray({
        context: context,
        attributes: [
          {
            index: 0,
            vertexBuffer: Buffer.createVertexBuffer({
              context: context,
              typedArray: new Float32Array([0, 0, 0, 1]),
              usage: BufferUsage.STATIC_DRAW,
            }),
            componentsPerAttribute: 4,
          },
        ],
      });

      var command = new DrawCommand({
        primitiveType: PrimitiveType.POINTS,
        shaderProgram: sp,
        vertexArray: va,
        instanceCount: -1,
      });

      expect(function () {
        command.execute(context);
      }).toThrowDeveloperError();
    });

    it("throws when instanceCount is greater than one and the instanced arrays extension is not supported", function () {
      if (!context.instancedArrays) {
        var vs =
          "attribute vec4 position; void main() { gl_PointSize = 1.0; gl_Position = position; }";
        var fs = "void main() { gl_FragColor = vec4(1.0); }";
        sp = ShaderProgram.fromCache({
          context: context,
          vertexShaderSource: vs,
          fragmentShaderSource: fs,
          attributeLocations: {
            position: 0,
          },
        });

        va = new VertexArray({
          context: context,
          attributes: [
            {
              index: 0,
              vertexBuffer: Buffer.createVertexBuffer({
                context: context,
                typedArray: new Float32Array([0, 0, 0, 1]),
                usage: BufferUsage.STATIC_DRAW,
              }),
              componentsPerAttribute: 4,
            },
          ],
        });

        var command = new DrawCommand({
          primitiveType: PrimitiveType.POINTS,
          shaderProgram: sp,
          vertexArray: va,
          instanceCount: 2,
        });

        expect(function () {
          command.execute(context);
        }).toThrowDeveloperError();
      }
    });
  },
  "WebGL"
);
