runProgram({ program, output, inputs, uniforms, supportsTextureFragments = false }) {
    if (!program) throw new Error('[WebGL2] missing program')
    if (!output) throw new Error('[WebGL2] missing output')
    if (!inputs) throw new Error('[WebGL2] missing inputs')

    const gl = this.context
    this.selectProgram(program)
    if (uniforms && Array.isArray(uniforms)) {
      this.bindUniforms(program, uniforms)
    }

    if (output.glTextureFragments) {
      if (!supportsTextureFragments) {
        throw new Error('[WebGL2] program does not support texture fragments')
      }

      const inputsWithFragments = inputs.filter(
        obj => obj.input.glTextureFragments && !obj.input.glTextureFragmentsAsColStack
      )
      const numFragments = output.glTextureFragments.length
      if (inputsWithFragments.some(obj => obj.input.glTextureFragments.length !== numFragments)) {
        throw new Error('[WebGL2] number of texture fragments in inputs and output do not match')
      }

      for (let k = 0; k < numFragments; k++) {
        this.bindOutputTexture(output.glTextureFragments[k], output.glTextureFragmentShape)
        this.bindInputTextures(program, inputs, k)
        gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)
      }
    } else {
      this.bindOutputTexture(output.glTexture, output.glTextureShape)
      this.bindInputTextures(program, inputs)
      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)
    }
  }
Runs fragment shader program, after binding output, inputs, and uniforms

@param {WebGLProgram} options.program
@param {Tensor} options.output
@param {Object[]} options.inputs
@param {Object[]} options.uniforms