@ 261106.14
current
/**
 * @license
 * Copyright 2010 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

var LibraryWebGL2 = {
  glGetStringi: (name, index) => {
    if (GL.currentContext.version < 2) {
      GL.recordError(0x502 /* GL_INVALID_OPERATION */); // Calling GLES3/WebGL2 function with a GLES2/WebGL1 context
      return 0;
    }
    var stringiCache = GL.stringiCache[name];
    if (stringiCache) {
      if (index < 0 || index >= stringiCache.length) {
        GL.recordError(0x501/*GL_INVALID_VALUE*/);
#if GL_ASSERTIONS
        err(`GL_INVALID_VALUE in glGetStringi: index out of range (${index})!`);
#endif
        return 0;
      }
      return stringiCache[index];
    }
    switch (name) {
      case 0x1F03 /* GL_EXTENSIONS */:
        var exts = webglGetExtensions().map(stringToNewUTF8);
        stringiCache = GL.stringiCache[name] = exts;
        if (index < 0 || index >= stringiCache.length) {
          GL.recordError(0x501/*GL_INVALID_VALUE*/);
#if GL_ASSERTIONS
          err(`GL_INVALID_VALUE in glGetStringi: index out of range (${index}) in a call to GL_EXTENSIONS!`);
#endif
          return 0;
        }
        return stringiCache[index];
      default:
        GL.recordError(0x500/*GL_INVALID_ENUM*/);
#if GL_ASSERTIONS
        err(`GL_INVALID_ENUM in glGetStringi: Unknown parameter ${name}!`);
#endif
        return 0;
    }
  },

  glGetInteger64v: (name_, p) => {
    emscriptenWebGLGet(name_, p, EM_FUNC_SIG_PARAM_J);
  },

  glGetInternalformativ: (target, internalformat, pname, bufSize, params) => {
#if GL_TRACK_ERRORS
    if (bufSize < 0) {
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetInternalformativ(target=${target}, internalformat=${internalformat}, pname=${pname}, bufSize=${bufSize}, params=${params}): Function called with bufSize < 0!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
    if (!params) {
      // GLES3 specification does not specify how to behave if values is a null pointer. Since calling this function does not make sense
      // if values == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetInternalformativ(target=${target}, internalformat=${internalformat}, pname=${pname}, bufSize=${bufSize}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    var ret = GLctx.getInternalformatParameter(target, internalformat, pname);
    if (ret === null) return;
    for (var i = 0; i < ret.length && i < bufSize; ++i) {
      {{{ makeSetValue('params', 'i*4', 'ret[i]', 'i32') }}};
    }
  },

  glCompressedTexImage3D: (target, level, internalFormat, width, height, depth, border, imageSize, data) => {
    if (GLctx.currentPixelUnpackBufferBinding) {
      GLctx.compressedTexImage3D(target, level, internalFormat, width, height, depth, border, imageSize, data);
    } else {
      GLctx.compressedTexImage3D(target, level, internalFormat, width, height, depth, border, HEAPU8, data, imageSize);
    }
  },

  glCompressedTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) => {
    if (GLctx.currentPixelUnpackBufferBinding) {
      GLctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data);
    } else {
      GLctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, HEAPU8, data, imageSize);
    }
  },

  glGetBufferParameteri64v: (target, value, data) => {
#if GL_TRACK_ERRORS
    if (!data) {
      // GLES2 specification does not specify how to behave if data is a null pointer. Since calling this function does not make sense
      // if data == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetBufferParameteri64v(target=${target}, value=${value}, data=0): Function called with null out data pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    writeI53ToI64(data, GLctx.getBufferParameter(target, value));
  },

  glGetBufferSubData: (target, offset, size, data) => {
#if GL_TRACK_ERRORS
    if (!data) {
      // GLES2 specification does not specify how to behave if data is a null pointer. Since calling this function does not make sense
      // if data == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetBufferSubData(target=${target}, offset=${offset}, size=${size}, data=0): Function called with null out data pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
#if WEBGL_USE_GARBAGE_FREE_APIS
    size && GLctx.getBufferSubData(target, offset, HEAPU8, data, size);
#else
    size && GLctx.getBufferSubData(target, offset, HEAPU8.subarray(data, data+size));
#endif
  },

  glInvalidateFramebuffer: (target, numAttachments, attachments) => {
#if GL_ASSERTIONS
    assert(numAttachments < tempFixedLengthArray.length, `Invalid count of numAttachments=${numAttachments} passed to glInvalidateFramebuffer (that many attachment points do not exist in GL)`);
#endif
    var list = tempFixedLengthArray[numAttachments];
    for (var i = 0; i < numAttachments; i++) {
      list[i] = {{{ makeGetValue('attachments', 'i*4', 'i32') }}};
    }

    GLctx.invalidateFramebuffer(target, list);
  },

  glInvalidateSubFramebuffer: (target, numAttachments, attachments, x, y, width, height) => {
#if GL_ASSERTIONS
    assert(numAttachments < tempFixedLengthArray.length, `Invalid count of numAttachments=${numAttachments} passed to glInvalidateSubFramebuffer (that many attachment points do not exist in GL)`);
#endif
    var list = tempFixedLengthArray[numAttachments];
    for (var i = 0; i < numAttachments; i++) {
      list[i] = {{{ makeGetValue('attachments', 'i*4', 'i32') }}};
    }

    GLctx.invalidateSubFramebuffer(target, list, x, y, width, height);
  },

  glTexImage3D: (target, level, internalFormat, width, height, depth, border, format, type, pixels) => {
    if (GLctx.currentPixelUnpackBufferBinding) {
      GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
    } else if (pixels) {
      var heap = heapObjectForWebGLType(type);
#if WEBGL_USE_GARBAGE_FREE_APIS
      GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, heap, toTypedArrayIndex(pixels, heap));
#else
      var pixelData = emscriptenWebGLGetTexPixelData(type, format, width, height * depth, pixels);
      GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixelData);
#endif
    } else {
      GLctx.texImage3D(target, level, internalFormat, width, height, depth, border, format, type, null);
    }
  },

  glTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels) => {
    if (GLctx.currentPixelUnpackBufferBinding) {
      GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
    } else if (pixels) {
      var heap = heapObjectForWebGLType(type);
#if WEBGL_USE_GARBAGE_FREE_APIS
      GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, heap, toTypedArrayIndex(pixels, heap));
#else
      var pixelData = emscriptenWebGLGetTexPixelData(type, format, width, height * depth, pixels);
      GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixelData);
#endif
    } else {
      GLctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, null);
    }
  },

  // Queries
  glGenQueries: (n, ids) => {
    GL.genObject(n, ids, 'createQuery', GL.queries
#if GL_ASSERTIONS
    , 'glGenQueries'
#endif
      );
  },

  glDeleteQueries: (n, ids) => {
    for (var i = 0; i < n; i++) {
      var id = {{{ makeGetValue('ids', 'i*4', 'i32') }}};
      var query = GL.queries[id];
      if (!query) continue; // GL spec: "unused names in ids are ignored, as is the name zero."
      GLctx.deleteQuery(query);
      GL.queries[id] = null;
    }
  },

  glIsQuery: (id) => {
    var query = GL.queries[id];
    if (!query) return 0;
    return GLctx.isQuery(query);
  },

  glBeginQuery: (target, id) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.queries, id, 'glBeginQuery', 'id');
#endif
    GLctx.beginQuery(target, GL.queries[id]);
  },

  glGetQueryiv: (target, pname, params) => {
#if GL_TRACK_ERRORS
    if (!params) {
      // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense
      // if p == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetQueryiv(target=${target}, pname=${pname}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    {{{ makeSetValue('params', '0', 'GLctx.getQuery(target, pname)', 'i32') }}};
  },

  glGetQueryObjectuiv: (id, pname, params) => {
#if GL_TRACK_ERRORS
    if (!params) {
      // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense
      // if p == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetQueryObjectuiv(id=${id}, pname=${pname}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.queries, id, 'glGetQueryObjectuiv', 'id');
#endif
    var query = GL.queries[id];
    var param = GLctx.getQueryParameter(query, pname);
    var ret;
    if (typeof param === 'boolean') {
      ret = param ? 1 : 0;
    } else {
      ret = param;
    }
    {{{ makeSetValue('params', '0', 'ret', 'i32') }}};
  },

  // Sampler objects
  glGenSamplers: (n, samplers) => {
    GL.genObject(n, samplers, 'createSampler', GL.samplers
#if GL_ASSERTIONS
    , 'glGenSamplers'
#endif
      );
  },

  glDeleteSamplers: (n, samplers) => {
    for (var i = 0; i < n; i++) {
      var id = {{{ makeGetValue('samplers', 'i*4', 'i32') }}};
      var sampler = GL.samplers[id];
      if (!sampler) continue;
      GLctx.deleteSampler(sampler);
      sampler.name = 0;
      GL.samplers[id] = null;
    }
  },

  glIsSampler: (id) => {
    var sampler = GL.samplers[id];
    if (!sampler) return 0;
    return GLctx.isSampler(sampler);
  },

  glBindSampler: (unit, sampler) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.samplers, sampler, 'glBindSampler', 'sampler');
#endif
    GLctx.bindSampler(unit, GL.samplers[sampler]);
  },

  glSamplerParameterf: (sampler, pname, param) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.samplers, sampler, 'glBindSampler', 'sampler');
#endif
    GLctx.samplerParameterf(GL.samplers[sampler], pname, param);
  },

  glSamplerParameteri: (sampler, pname, param) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.samplers, sampler, 'glBindSampler', 'sampler');
#endif
    GLctx.samplerParameteri(GL.samplers[sampler], pname, param);
  },

  glSamplerParameterfv: (sampler, pname, params) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.samplers, sampler, 'glBindSampler', 'sampler');
#endif
    var param = {{{ makeGetValue('params', '0', 'float') }}};
    GLctx.samplerParameterf(GL.samplers[sampler], pname, param);
  },

  glSamplerParameteriv: (sampler, pname, params) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.samplers, sampler, 'glBindSampler', 'sampler');
#endif
    var param = {{{ makeGetValue('params', '0', 'i32') }}};
    GLctx.samplerParameteri(GL.samplers[sampler], pname, param);
  },

  glGetSamplerParameterfv: (sampler, pname, params) => {
#if GL_TRACK_ERRORS
    if (!params) {
      // GLES3 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense
      // if p == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetSamplerParameterfv(sampler=${sampler}, pname=${pname}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    {{{ makeSetValue('params', '0', 'GLctx.getSamplerParameter(GL.samplers[sampler], pname)', 'float') }}};
  },

  glGetSamplerParameteriv: (sampler, pname, params) => {
#if GL_TRACK_ERRORS
    if (!params) {
      // GLES3 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense
      // if p == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetSamplerParameteriv(sampler=${sampler}, pname=${pname}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    {{{ makeSetValue('params', '0', 'GLctx.getSamplerParameter(GL.samplers[sampler], pname)', 'i32') }}};
  },

  // Transform Feedback
  glGenTransformFeedbacks: (n, ids) => {
    GL.genObject(n, ids, 'createTransformFeedback', GL.transformFeedbacks
#if GL_ASSERTIONS
    , 'glGenTransformFeedbacks'
#endif
      );
  },

  glDeleteTransformFeedbacks: (n, ids) => {
    for (var i = 0; i < n; i++) {
      var id = {{{ makeGetValue('ids', 'i*4', 'i32') }}};
      var transformFeedback = GL.transformFeedbacks[id];
      if (!transformFeedback) continue; // GL spec: "unused names in ids are ignored, as is the name zero."
      GLctx.deleteTransformFeedback(transformFeedback);
      transformFeedback.name = 0;
      GL.transformFeedbacks[id] = null;
    }
  },

  glIsTransformFeedback: (id) => GLctx.isTransformFeedback(GL.transformFeedbacks[id]),

  glBindTransformFeedback: (target, id) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.transformFeedbacks, id, 'glBindTransformFeedback', 'id');
#endif
    GLctx.bindTransformFeedback(target, GL.transformFeedbacks[id]);
  },

  glTransformFeedbackVaryings: (program, count, varyings, bufferMode) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glTransformFeedbackVaryings', 'program');
#endif
    program = GL.programs[program];
    var vars = [];
    for (var i = 0; i < count; i++)
      vars.push(UTF8ToString({{{ makeGetValue('varyings', 'i*' + POINTER_SIZE, '*') }}}));

    GLctx.transformFeedbackVaryings(program, vars, bufferMode);
  },

  glGetTransformFeedbackVarying: (program, index, bufSize, length, size, type, name) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetTransformFeedbackVarying', 'program');
#endif
    program = GL.programs[program];
    var info = GLctx.getTransformFeedbackVarying(program, index);
    if (!info) return; // If an error occurred, the return parameters length, size, type and name will be unmodified.

    if (name && bufSize > 0) {
      var numBytesWrittenExclNull = stringToUTF8(info.name, name, bufSize);
      if (length) {{{ makeSetValue('length', '0', 'numBytesWrittenExclNull', 'i32') }}};
    } else {
      if (length) {{{ makeSetValue('length', '0', 0, 'i32') }}};
    }

    if (size) {{{ makeSetValue('size', '0', 'info.size', 'i32') }}};
    if (type) {{{ makeSetValue('type', '0', 'info.type', 'i32') }}};
  },

  $emscriptenWebGLGetIndexed: (target, index, data, type) => {
#if GL_TRACK_ERRORS
    if (!data) {
      // GLES2 specification does not specify how to behave if data is a null pointer. Since calling this function does not make sense
      // if data == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetInteger(64)i_v(target=${target}, index=${index}, data=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    var result = GLctx.getIndexedParameter(target, index);
    var ret;
    switch (typeof result) {
      case 'boolean':
        ret = result ? 1 : 0;
        break;
      case 'number':
        ret = result;
        break;
      case 'object':
        if (result === null) {
          switch (target) {
            case 0x8C8F: // TRANSFORM_FEEDBACK_BUFFER_BINDING
            case 0x8A28: // UNIFORM_BUFFER_BINDING
              ret = 0;
              break;
            default: {
              GL.recordError(0x500); // GL_INVALID_ENUM
#if GL_ASSERTIONS
              err('GL_INVALID_ENUM in glGetInteger(64)i_v(' + target + ') and it returns null!');
#endif
              return;
            }
          }
        } else if (result instanceof WebGLBuffer) {
          ret = result.name | 0;
        } else {
          GL.recordError(0x500); // GL_INVALID_ENUM
#if GL_ASSERTIONS
          err('GL_INVALID_ENUM in glGetInteger(64)i_v: Unknown object returned from WebGL getIndexedParameter(' + target + ')!');
#endif
          return;
        }
        break;
      default:
        GL.recordError(0x500); // GL_INVALID_ENUM
#if GL_ASSERTIONS
        err('GL_INVALID_ENUM in glGetInteger(64)i_v: Native code calling glGetInteger(64)i_v(' + target + ') and it returns ' + result + ' of type ' + typeof(result) + '!');
#endif
        return;
    }

    switch (type) {
      case EM_FUNC_SIG_PARAM_J: writeI53ToI64(data, ret); break;
      case EM_FUNC_SIG_PARAM_I: {{{ makeSetValue('data', '0', 'ret', 'i32') }}}; break;
      case EM_FUNC_SIG_PARAM_F: {{{ makeSetValue('data', '0', 'ret', 'float') }}}; break;
      case EM_FUNC_SIG_PARAM_B: {{{ makeSetValue('data', '0', 'ret ? 1 : 0', 'i8') }}}; break;
      default: abort('internal emscriptenWebGLGetIndexed() error, bad type: ' + type);
    }
  },

  glGetIntegeri_v: (target, index, data) =>
    emscriptenWebGLGetIndexed(target, index, data, EM_FUNC_SIG_PARAM_I),

  glGetInteger64i_v: (target, index, data) =>
    emscriptenWebGLGetIndexed(target, index, data, EM_FUNC_SIG_PARAM_J),

  // Uniform Buffer objects
  glBindBufferBase: (target, index, buffer) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.buffers, buffer, 'glBindBufferBase', 'buffer');
#endif
    GLctx.bindBufferBase(target, index, GL.buffers[buffer]);
  },

  glBindBufferRange: (target, index, buffer, offset, ptrsize) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.buffers, buffer, 'glBindBufferRange', 'buffer');
#endif
    GLctx.bindBufferRange(target, index, GL.buffers[buffer], offset, ptrsize);
  },

  glGetUniformIndices: (program, uniformCount, uniformNames, uniformIndices) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetUniformIndices', 'program');
#endif
#if GL_TRACK_ERRORS
    if (!uniformIndices) {
      // GLES2 specification does not specify how to behave if uniformIndices is a null pointer. Since calling this function does not make sense
      // if uniformIndices == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetUniformIndices(program=${program}, uniformCount=${uniformCount}, uniformNames=${uniformNames}, uniformIndices=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
    if (uniformCount > 0 && (uniformNames == 0 || uniformIndices == 0)) {
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    program = GL.programs[program];
    var names = [];
    for (var i = 0; i < uniformCount; i++)
      names.push(UTF8ToString({{{ makeGetValue('uniformNames', 'i*' + POINTER_SIZE, '*') }}}));

    var result = GLctx.getUniformIndices(program, names);
    if (!result) return; // GL spec: If an error is generated, nothing is written out to uniformIndices.

    var len = result.length;
    for (var i = 0; i < len; i++) {
      {{{ makeSetValue('uniformIndices', 'i*4', 'result[i]', 'i32') }}};
    }
  },

  glGetActiveUniformsiv: (program, uniformCount, uniformIndices, pname, params) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetActiveUniformsiv', 'program');
#endif
#if GL_TRACK_ERRORS
    if (!params) {
      // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense
      // if params == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetActiveUniformsiv(program=${program}, uniformCount=${uniformCount}, uniformIndices=${uniformIndices}, pname=${pname}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
    if (uniformCount > 0 && uniformIndices == 0) {
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    program = GL.programs[program];
    var ids = [];
    for (var i = 0; i < uniformCount; i++) {
      ids.push({{{ makeGetValue('uniformIndices', 'i*4', 'i32') }}});
    }

    var result = GLctx.getActiveUniforms(program, ids, pname);
    if (!result) return; // GL spec: If an error is generated, nothing is written out to params.

    var len = result.length;
    for (var i = 0; i < len; i++) {
      {{{ makeSetValue('params', 'i*4', 'result[i]', 'i32') }}};
    }
  },

  glGetUniformBlockIndex: (program, uniformBlockName) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetUniformBlockIndex', 'program');
#endif
    return GLctx.getUniformBlockIndex(GL.programs[program], UTF8ToString(uniformBlockName));
  },

  glGetActiveUniformBlockiv: (program, uniformBlockIndex, pname, params) => {
#if GL_TRACK_ERRORS
    if (!params) {
      // GLES2 specification does not specify how to behave if params is a null pointer. Since calling this function does not make sense
      // if params == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetActiveUniformBlockiv(program=${program}, uniformBlockIndex=${uniformBlockIndex}, pname=${pname}, params=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetActiveUniformBlockiv', 'program');
#endif
    program = GL.programs[program];

    if (pname == 0x8A41 /* GL_UNIFORM_BLOCK_NAME_LENGTH */) {
      var name = GLctx.getActiveUniformBlockName(program, uniformBlockIndex);
      {{{ makeSetValue('params', 0, 'name.length+1', 'i32') }}};
      return;
    }

    var result = GLctx.getActiveUniformBlockParameter(program, uniformBlockIndex, pname);
    if (result === null) return; // If an error occurs, nothing should be written to params.
    if (pname == 0x8A43 /*GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES*/) {
      for (var i = 0; i < result.length; i++) {
        {{{ makeSetValue('params', 'i*4', 'result[i]', 'i32') }}};
      }
    } else {
      {{{ makeSetValue('params', '0', 'result', 'i32') }}};
    }
  },

  glGetActiveUniformBlockName: (program, uniformBlockIndex, bufSize, length, uniformBlockName) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetActiveUniformBlockName', 'program');
#endif
    program = GL.programs[program];

    var result = GLctx.getActiveUniformBlockName(program, uniformBlockIndex);
    if (!result) return; // If an error occurs, nothing will be written to uniformBlockName or length.
    if (uniformBlockName && bufSize > 0) {
      var numBytesWrittenExclNull = stringToUTF8(result, uniformBlockName, bufSize);
      if (length) {{{ makeSetValue('length', '0', 'numBytesWrittenExclNull', 'i32') }}};
    } else {
      if (length) {{{ makeSetValue('length', '0', 0, 'i32') }}};
    }
  },

  glUniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glUniformBlockBinding', 'program');
#endif
    program = GL.programs[program];

    GLctx.uniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding);
  },

  glClearBufferiv: (buffer, drawbuffer, value) => {
#if GL_ASSERTIONS
    assert((value & 3) == 0, 'pointer passed to glClearBufferiv must be 4-byte aligned');
#endif

    GLctx.clearBufferiv(buffer, drawbuffer, HEAP32, {{{ getHeapOffset('value', 'i32') }}});
  },

  glClearBufferuiv: (buffer, drawbuffer, value) => {
#if GL_ASSERTIONS
    assert((value & 3) == 0, 'pointer passed to glClearBufferuiv must be 4-byte aligned');
#endif

    GLctx.clearBufferuiv(buffer, drawbuffer, HEAPU32, {{{ getHeapOffset('value', 'u32') }}});
  },

  glClearBufferfv: (buffer, drawbuffer, value) => {
#if GL_ASSERTIONS
    assert((value & 3) == 0, 'pointer passed to glClearBufferfv must be 4-byte aligned');
#endif

    GLctx.clearBufferfv(buffer, drawbuffer, HEAPF32, {{{ getHeapOffset('value', 'float') }}});
  },

  glFenceSync: (condition, flags) => {
    var sync = GLctx.fenceSync(condition, flags);
    if (sync) {
      var id = GL.getNewId(GL.syncs);
      sync.name = id;
      GL.syncs[id] = sync;
      return id;
    }
    return 0; // Failed to create a sync object
  },

  glDeleteSync: (id) => {
    if (!id) return;
    var sync = GL.syncs[id];
    if (!sync) { // glDeleteSync signals an error when deleting a nonexisting object, unlike some other GL delete functions.
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
    GLctx.deleteSync(sync);
    sync.name = 0;
    GL.syncs[id] = null;
  },

  glClientWaitSync: (sync, flags, {{{ defineI64Param('timeout') }}}) => {
    // WebGL2 vs GLES3 differences: in GLES3, the timeout parameter is a uint64, where 0xFFFFFFFFFFFFFFFFULL means GL_TIMEOUT_IGNORED.
    // In JS, there's no 64-bit value types, so instead timeout is taken to be signed, and GL_TIMEOUT_IGNORED is given value -1.
    // Inherently the value accepted in the timeout is lossy, and can't take in arbitrary u64 bit pattern (but most likely doesn't matter)
    // See https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.15
    {{{ receiveI64ParamAsI53Unchecked('timeout'); }}}
    return GLctx.clientWaitSync(GL.syncs[sync], flags, timeout);
  },

  glWaitSync: (sync, flags, {{{ defineI64Param('timeout') }}}) => {
    // See WebGL2 vs GLES3 difference on GL_TIMEOUT_IGNORED above (https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.15)
    {{{ receiveI64ParamAsI53Unchecked('timeout'); }}}
    GLctx.waitSync(GL.syncs[sync], flags, timeout);
  },

  glGetSynciv: (sync, pname, bufSize, length, values) => {
#if GL_TRACK_ERRORS
    if (bufSize < 0) {
      // GLES3 specification does not specify how to behave if bufSize < 0, however in the spec wording for glGetInternalformativ, it does say that GL_INVALID_VALUE should be raised,
      // so raise GL_INVALID_VALUE here as well.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetSynciv(sync=${sync}, pname=${pname}, bufSize=${bufSize}, length=${length}, values=${values}): Function called with bufSize < 0!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
    if (!values) {
      // GLES3 specification does not specify how to behave if values is a null pointer. Since calling this function does not make sense
      // if values == null, issue a GL error to notify user about it.
#if GL_ASSERTIONS
      err(`GL_INVALID_VALUE in glGetSynciv(sync=${sync}, pname=${pname}, bufSize=${bufSize}, length=${length}, values=0): Function called with null out pointer!`);
#endif
      GL.recordError(0x501 /* GL_INVALID_VALUE */);
      return;
    }
#endif
    var ret = GLctx.getSyncParameter(GL.syncs[sync], pname);
    if (ret !== null) {
      {{{ makeSetValue('values', '0', 'ret', 'i32') }}};
      if (length) {{{ makeSetValue('length', '0', '1', 'i32') }}}; // Report a single value outputted.
    }
  },

  glIsSync: (sync) => GLctx.isSync(GL.syncs[sync]),

  glGetUniformuiv: (program, location, params) =>
    emscriptenWebGLGetUniform(program, location, params, EM_FUNC_SIG_PARAM_I),

  glGetFragDataLocation: (program, name) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.programs, program, 'glGetFragDataLocation', 'program');
#endif
    return GLctx.getFragDataLocation(GL.programs[program], UTF8ToString(name));
  },

  glGetVertexAttribIiv: (index, pname, params) => {
    // N.B. This function may only be called if the vertex attribute was specified using the function glVertexAttribI4iv(),
    // otherwise the results are undefined. (GLES3 spec 6.1.12)
    emscriptenWebGLGetVertexAttrib(index, pname, params, EM_FUNC_SIG_PARAM_I);
  },

  // N.B. This function may only be called if the vertex attribute was specified using the function glVertexAttribI4uiv(),
  // otherwise the results are undefined. (GLES3 spec 6.1.12)
  glGetVertexAttribIuiv: 'glGetVertexAttribIiv',

  glUniform1ui: (location, v0) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform1ui', 'location');
#endif
    GLctx.uniform1ui(webglGetUniformLocation(location), v0);
  },

  glUniform2ui: (location, v0, v1) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform2ui', 'location');
#endif
    GLctx.uniform2ui(webglGetUniformLocation(location), v0, v1);
  },

  glUniform3ui: (location, v0, v1, v2) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform3ui', 'location');
#endif
    GLctx.uniform3ui(webglGetUniformLocation(location), v0, v1, v2);
  },

  glUniform4ui: (location, v0, v1, v2, v3) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform4ui', 'location');
#endif
    GLctx.uniform4ui(webglGetUniformLocation(location), v0, v1, v2, v3);
  },

  glUniform1uiv: (location, count, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform1uiv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniform1uiv must be 4-byte aligned');
#endif
    count && GLctx.uniform1uiv(webglGetUniformLocation(location), HEAPU32, {{{ getHeapOffset('value', 'u32') }}}, count);
  },

  glUniform2uiv: (location, count, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform2uiv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniform2uiv must be 4-byte aligned');
#endif
    count && GLctx.uniform2uiv(webglGetUniformLocation(location), HEAPU32, {{{ getHeapOffset('value', 'u32') }}}, count*2);
  },

  glUniform3uiv: (location, count, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform3uiv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniform3uiv must be 4-byte aligned');
#endif
    count && GLctx.uniform3uiv(webglGetUniformLocation(location), HEAPU32, {{{ getHeapOffset('value', 'u32') }}}, count*3);
  },

  glUniform4uiv: (location, count, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniform4uiv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniform4uiv must be 4-byte aligned');
#endif
    count && GLctx.uniform4uiv(webglGetUniformLocation(location), HEAPU32, {{{ getHeapOffset('value', 'u32') }}}, count*4);
  },

  glUniformMatrix2x3fv: (location, count, transpose, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniformMatrix2x3fv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniformMatrix2x3fv must be 4-byte aligned');
#endif
    count && GLctx.uniformMatrix2x3fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*6);
  },

  glUniformMatrix3x2fv: (location, count, transpose, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniformMatrix3x2fv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniformMatrix3x2fv must be 4-byte aligned');
#endif
    count && GLctx.uniformMatrix3x2fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*6);
  },

  glUniformMatrix2x4fv: (location, count, transpose, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniformMatrix2x4fv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniformMatrix2x4fv must be 4-byte aligned');
#endif
    count && GLctx.uniformMatrix2x4fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*8);
  },

  glUniformMatrix4x2fv: (location, count, transpose, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniformMatrix4x2fv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniformMatrix4x2fv must be 4-byte aligned');
#endif
    count && GLctx.uniformMatrix4x2fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*8);
  },

  glUniformMatrix3x4fv: (location, count, transpose, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniformMatrix3x4fv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniformMatrix3x4fv must be 4-byte aligned');
#endif
    count && GLctx.uniformMatrix3x4fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*12);
  },

  glUniformMatrix4x3fv: (location, count, transpose, value) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GLctx.currentProgram.uniformLocsById, location, 'glUniformMatrix4x3fv', 'location');
    assert((value & 3) == 0, 'pointer passed to glUniformMatrix4x3fv must be 4-byte aligned');
#endif
    count && GLctx.uniformMatrix4x3fv(webglGetUniformLocation(location), !!transpose, HEAPF32, {{{ getHeapOffset('value', 'float') }}}, count*12);
  },

  glVertexAttribI4iv: (index, v) => {
#if GL_ASSERTIONS
    assert((v & 3) == 0, 'pointer passed to glVertexAttribI4iv must be 4-byte aligned');
    assert(v != 0, 'null pointer passed to glVertexAttribI4iv');
#endif
    GLctx.vertexAttribI4i(index, HEAP32[v>>2], HEAP32[v+4>>2], HEAP32[v+8>>2], HEAP32[v+12>>2]);
  },

  glVertexAttribI4uiv: (index, v) => {
#if GL_ASSERTIONS
    assert((v & 3) == 0, 'pointer passed to glVertexAttribI4uiv must be 4-byte aligned');
    assert(v != 0, 'null pointer passed to glVertexAttribI4uiv');
#endif
    GLctx.vertexAttribI4ui(index, HEAPU32[v>>2], HEAPU32[v+4>>2], HEAPU32[v+8>>2], HEAPU32[v+12>>2]);
  },

  glProgramParameteri: (program, pname, value) => {
    GL.recordError(0x500/*GL_INVALID_ENUM*/);
#if GL_ASSERTIONS
    err('GL_INVALID_ENUM in glProgramParameteri: WebGL does not support binary shader formats! Calls to glProgramParameteri always fail. See https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.4');
#endif
  },

  glGetProgramBinary: (program, bufSize, length, binaryFormat, binary) => {
    GL.recordError(0x502/*GL_INVALID_OPERATION*/);
#if GL_ASSERTIONS
    err('GL_INVALID_OPERATION in glGetProgramBinary: WebGL does not support binary shader formats! Calls to glGetProgramBinary always fail. See https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.4');
#endif
  },

  glProgramBinary: (program, binaryFormat, binary, length) => {
    GL.recordError(0x500/*GL_INVALID_ENUM*/);
#if GL_ASSERTIONS
    err('GL_INVALID_ENUM in glProgramBinary: WebGL does not support binary shader formats! Calls to glProgramBinary always fail. See https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.4');
#endif
  },

  glFramebufferTextureLayer: (target, attachment, texture, level, layer) => {
#if GL_ASSERTIONS
    GL.validateGLObjectID(GL.textures, texture, 'glFramebufferTextureLayer', 'texture');
#endif
    GLctx.framebufferTextureLayer(target, attachment, GL.textures[texture], level, layer);
  },

  glVertexAttribIPointer: (index, size, type, stride, ptr) => {
#if FULL_ES3
    var cb = GL.currentContext.clientBuffers[index];
#if GL_ASSERTIONS
    assert(cb, index);
#endif
    if (!GLctx.currentArrayBufferBinding) {
      cb.size = size;
      cb.type = type;
      cb.normalized = false;
      cb.stride = stride;
      cb.ptr = ptr;
      cb.clientside = true;
      cb.vertexAttribPointerAdaptor = /** @this {WebGLRenderingContext} */ function(index, size, type, normalized, stride, ptr) {
        this.vertexAttribIPointer(index, size, type, stride, ptr);
      };
      return;
    }
    cb.clientside = false;
#endif
#if GL_ASSERTIONS
    GL.validateVertexAttribPointer(size, type, stride, ptr);
#endif
    GLctx.vertexAttribIPointer(index, size, type, stride, ptr);
  },

#if !LEGACY_GL_EMULATION
  // Defined in library_glemu.js when LEGACY_GL_EMULATION is set
  glDrawRangeElements: (mode, start, end, count, type, indices) => {
    // TODO: This should be a trivial pass-through function registered at the bottom of this page as
    // glFuncs[6][1] += ' drawRangeElements';
    // but due to https://bugzil.la/1202427,
    // we work around by ignoring the range.
    _glDrawElements(mode, count, type, indices);
  },
#endif

  glDrawArraysInstancedBaseInstanceWEBGL__sig: 'viiiii',
  glDrawArraysInstancedBaseInstanceWEBGL: (mode, first, count, instanceCount, baseInstance) => {
    GLctx.dibvbi['drawArraysInstancedBaseInstanceWEBGL'](mode, first, count, instanceCount, baseInstance);
  },
  glDrawArraysInstancedBaseInstance: 'glDrawArraysInstancedBaseInstanceWEBGL',
  glDrawArraysInstancedBaseInstanceANGLE: 'glDrawArraysInstancedBaseInstanceWEBGL',

  glDrawElementsInstancedBaseVertexBaseInstanceWEBGL__sig: 'viiiiiii',
  glDrawElementsInstancedBaseVertexBaseInstanceWEBGL: (mode, count, type, offset, instanceCount, baseVertex, baseinstance) => {
    GLctx.dibvbi['drawElementsInstancedBaseVertexBaseInstanceWEBGL'](mode, count, type, offset, instanceCount, baseVertex, baseinstance);
  },
  glDrawElementsInstancedBaseVertexBaseInstanceANGLE: 'glDrawElementsInstancedBaseVertexBaseInstanceWEBGL',

  $webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance: (ctx) =>
    // Closure is expected to be allowed to minify the '.dibvbi' property, so not accessing it quoted.
    !!(ctx.dibvbi = ctx.getExtension('WEBGL_draw_instanced_base_vertex_base_instance')),

  emscripten_webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance: (ctx) =>
    webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GL.contexts[ctx].GLctx),

  glMultiDrawArraysInstancedBaseInstanceWEBGL__sig: 'viiiiii',
  glMultiDrawArraysInstancedBaseInstanceWEBGL: (mode, firsts, counts, instanceCounts, baseInstances, drawCount) => {
    GLctx.mdibvbi['multiDrawArraysInstancedBaseInstanceWEBGL'](
      mode,
      HEAP32,
      {{{ getHeapOffset('firsts', 'i32') }}},
      HEAP32,
      {{{ getHeapOffset('counts', 'i32') }}},
      HEAP32,
      {{{ getHeapOffset('instanceCounts', 'i32') }}},
      HEAPU32,
      {{{ getHeapOffset('baseInstances', 'i32') }}},
      drawCount);
  },
  glMultiDrawArraysInstancedBaseInstanceANGLE: 'glMultiDrawArraysInstancedBaseInstanceWEBGL',

  glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL__sig: 'viiiiiiii',
  glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL: (mode, counts, type, offsets, instanceCounts, baseVertices, baseInstances, drawCount) => {
    GLctx.mdibvbi['multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL'](
      mode,
      HEAP32,
      {{{ getHeapOffset('counts', 'i32') }}},
      type,
      HEAP32,
      {{{ getHeapOffset('offsets', 'i32') }}},
      HEAP32,
      {{{ getHeapOffset('instanceCounts', 'i32') }}},
      HEAP32,
      {{{ getHeapOffset('baseVertices', 'i32') }}},
      HEAPU32,
      {{{ getHeapOffset('baseInstances', 'i32') }}},
      drawCount);
  },
  glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE: 'glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL',

  $webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance: (ctx) => {
    // Closure is expected to be allowed to minify the '.mdibvbi' property, so not accessing it quoted.
    return !!(ctx.mdibvbi = ctx.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance'));
  },

  emscripten_webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance: (ctx) =>
    webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GL.contexts[ctx].GLctx),
};

#if MAX_WEBGL_VERSION >= 2

// Simple pass-through functions.
// - Starred ones have return values.
// - [X] ones have X in the C name but not in the JS name
var webgl2PassthroughFuncs = [
  [0, 'endTransformFeedback pauseTransformFeedback resumeTransformFeedback'],
  [1, 'beginTransformFeedback readBuffer endQuery'],
  [4, 'clearBufferfi'],
  [5, 'vertexAttribI4i vertexAttribI4ui copyBufferSubData texStorage2D renderbufferStorageMultisample'],
  [6, 'texStorage3D'],
  [9, 'copyTexSubImage3D'],
  [10, 'blitFramebuffer']
];

// If user passes -sMAX_WEBGL_VERSION >= 2 -sSTRICT but not -lGL (to link in
// WebGL 1), then WebGL2 library should not be linked in as well.
if (typeof createGLPassthroughFunctions === 'undefined') {
  error('In order to use WebGL 2 in strict mode with -sMAX_WEBGL_VERSION=2, you need to link in WebGL support with -lGL');
}

createGLPassthroughFunctions(LibraryWebGL2, webgl2PassthroughFuncs);

recordGLProcAddressGet(LibraryWebGL2);

addToLibrary(LibraryWebGL2);

#endif