text: var GL_QUERY_RESULT_EXT = 0x8866
  var GL_QUERY_RESULT_AVAILABLE_EXT = 0x8867
  var GL_TIME_ELAPSED_EXT = 0x88BF
  
  module.exports = function (gl, extensions) {
    if (!extensions.ext_disjoint_timer_query) {
      return null
    }
  
    // QUERY POOL BEGIN
    var queryPool = []
    function allocQuery () {
      return queryPool.pop() || extensions.ext_disjoint_timer_query.createQueryEXT()
    }
    function freeQuery (query) {
      queryPool.push(query)
    }
    // QUERY POOL END
  
    var pendingQueries = []
    function beginQuery (stats) {
      var query = allocQuery()
      extensions.ext_disjoint_timer_query.beginQueryEXT(GL_TIME_ELAPSED_EXT, query)
      pendingQueries.push(query)
      pushScopeStats(pendingQueries.length - 1, pendingQueries.length, stats)
    }
  
    function endQuery () {
      extensions.ext_disjoint_timer_query.endQueryEXT(GL_TIME_ELAPSED_EXT)
    }
  
    //
    // Pending stats pool.
    //
    function PendingStats () {
      this.startQueryIndex = -1
      this.endQueryIndex = -1
      this.sum = 0
      this.stats = null
    }
    var pendingStatsPool = []
    function allocPendingStats () {
      return pendingStatsPool.pop() || new PendingStats()
    }
    function freePendingStats (pendingStats) {
      pendingStatsPool.push(pendingStats)
    }
    // Pending stats pool end
  
    var pendingStats = []
    function pushScopeStats (start, end, stats) {
      var ps = allocPendingStats()
      ps.startQueryIndex = start
      ps.endQueryIndex = end
      ps.sum = 0
      ps.stats = stats
      pendingStats.push(ps)
    }
  
    // we should call this at the beginning of the frame,
    // in order to update gpuTime
    var timeSum = []
    var queryPtr = []
    function update () {
      var ptr, i
  
      var n = pendingQueries.length
      if (n === 0) {
        return
      }
  
      // Reserve space
      queryPtr.length = Math.max(queryPtr.length, n + 1)
      timeSum.length = Math.max(timeSum.length, n + 1)
      timeSum[0] = 0
      queryPtr[0] = 0
  
      // Update all pending timer queries
      var queryTime = 0
      ptr = 0
      for (i = 0; i < pendingQueries.length; ++i) {
        var query = pendingQueries[i]
        if (extensions.ext_disjoint_timer_query.getQueryObjectEXT(query, GL_QUERY_RESULT_AVAILABLE_EXT)) {
          queryTime += extensions.ext_disjoint_timer_query.getQueryObjectEXT(query, GL_QUERY_RESULT_EXT)
          freeQuery(query)
        } else {
          pendingQueries[ptr++] = query
        }
        timeSum[i + 1] = queryTime
        queryPtr[i + 1] = ptr
      }
      pendingQueries.length = ptr
  
      // Update all pending stat queries
      ptr = 0
      for (i = 0; i < pendingStats.length; ++i) {
        var stats = pendingStats[i]
        var start = stats.startQueryIndex
        var end = stats.endQueryIndex
        stats.sum += timeSum[end] - timeSum[start]
        var startPtr = queryPtr[start]
        var endPtr = queryPtr[end]
        if (endPtr === startPtr) {
          stats.stats.gpuTime += stats.sum / 1e6
          freePendingStats(stats)
        } else {
          stats.startQueryIndex = startPtr
          stats.endQueryIndex = endPtr
          pendingStats[ptr++] = stats
        }
      }
      pendingStats.length = ptr
    }
  
    return {
      beginQuery: beginQuery,
      endQuery: endQuery,
      pushScopeStats: pushScopeStats,
      update: update,
      getNumPendingQueries: function () {
        return pendingQueries.length
      },
      clear: function () {
        queryPool.push.apply(queryPool, pendingQueries)
        for (var i = 0; i < queryPool.length; i++) {
          extensions.ext_disjoint_timer_query.deleteQueryEXT(queryPool[i])
        }
        pendingQueries.length = 0
        queryPool.length = 0
      },
      restore: function () {
        pendingQueries.length = 0
        queryPool.length = 0
      }
    }
  }
  