function upgradeShaders(vertexShader, fragmentShader, instanceUniforms) {
  // See what gets used
  let usesModelMatrix = modelMatrixRefRE.test(vertexShader)
  let usesModelViewMatrix = modelViewMatrixRefRE.test(vertexShader)
  let usesNormalMatrix = normalMatrixRefRE.test(vertexShader)

  // Find what uniforms are declared in which shader and their types
  let vertexUniforms = getShaderUniformTypes(vertexShader)
  let fragmentUniforms = getShaderUniformTypes(fragmentShader)

  let vertexDeclarations = [vertexCommonDefs]
  let vertexAssignments = []
  let fragmentDeclarations = []

  // Add variable assignments for, and rewrite references to, builtin matrices
  if (usesModelMatrix || usesModelViewMatrix || usesNormalMatrix) {
    vertexShader = vertexShader.replace(modelMatrixRefRE, attrRefReplacer)
    vertexAssignments.push(modelMatrixVarAssignment)
  }
  if (usesModelViewMatrix || usesNormalMatrix) {
    vertexShader = vertexShader.replace(modelViewMatrixRefRE, attrRefReplacer)
    vertexAssignments.push(modelViewMatrixVarAssignment)
  }
  if (usesNormalMatrix) {
    vertexShader = vertexShader.replace(normalMatrixRefRE, attrRefReplacer)
    vertexAssignments.push(normalMatrixVarAssignment)
    // Add the inverse() glsl polyfill if there isn't already one defined
    if (!/\binverse\s*\(/.test(vertexShader)) {
      vertexDeclarations.push(inverseFunction)
    }
  }

  // Add attributes and varyings for, and rewrite references to, instanceUniforms
  if (instanceUniforms) {
    instanceUniforms.forEach(name => {
      let vertType = vertexUniforms[name]
      let fragType = fragmentUniforms[name]
      if (vertType || fragType) {
        let finder = new RegExp(`\\b${name}\\b`, 'g')
        vertexDeclarations.push(`attribute ${vertType || fragType} troika_${name};`)
        if (vertType) {
          vertexShader = vertexShader.replace(finder, attrRefReplacer)
        }
        if (fragType) {
          fragmentShader = fragmentShader.replace(finder, varyingRefReplacer)
          let varyingDecl = `varying ${fragType} troika_vary_${name};`
          vertexDeclarations.push(varyingDecl)
          fragmentDeclarations.push(varyingDecl)
          vertexAssignments.push(`troika_vary_${name} = troika_${name};`)
        }
      }
    })
  }

  // Inject vertex shader declarations and assignments
  vertexShader = `
${vertexDeclarations.join('\n')}
${vertexShader.replace(voidMainRegExp, `
  $&
  ${ vertexAssignments.join('\n') }
`)}`

  // Inject fragment shader declarations
  if (fragmentDeclarations.length) {
    fragmentShader = `
${fragmentDeclarations.join('\n')}
${fragmentShader}`
  }

  return {vertexShader, fragmentShader}
}
Transform the given vertex and fragment shader pair so they accept instancing
attributes for the builtin matrix uniforms as well as any other uniforms that
have been declared as instanceable.