C++程序  |  800行  |  29.53 KB

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "device_info/device_info.h"

#include <sys/system_properties.h>
#include <EGL/egl.h>
#include <GLES3/gl32.h>

#include <cstdint>
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <set>

namespace {
using ProtoInfoWithErrors = androidgamesdk_deviceinfo::InfoWithErrors;
using ProtoErrors         = androidgamesdk_deviceinfo::Errors;
using ProtoInfo           = androidgamesdk_deviceinfo::Info;
using ProtoCpuCore        = androidgamesdk_deviceinfo::Info::CpuCore;
using ProtoGl             = androidgamesdk_deviceinfo::Info::Gl;

// size of GL view and texture in future
constexpr int VIEW_WIDTH = 8;
constexpr int VIEW_HEIGHT = VIEW_WIDTH;

namespace string_util {
bool startsWith(const std::string& text, const std::string& start) {
  return text.compare(0, start.length(), start) == 0;
}

void splitAdd(const std::string& toSplit, char delimeter,
              std::set<std::string>* result) {
  std::istringstream istr(toSplit);
  std::string piece;
  while (std::getline(istr, piece, delimeter)) {
    if (piece.length() > 0) {
      result->insert(piece);
    }
  }
}
}  // namespace string_util

template <typename T>
T readFile(const std::string& fileName, const T& error) {
  std::ifstream reader(fileName);
  if (reader.fail()) return error;
  T result;
  reader >> result;
  if (reader.fail()) return error;
  return result;
}

std::string readCpuPresent() {
  const std::string ERROR = "ERROR";
  return readFile("/sys/devices/system/cpu/present", ERROR);
}
std::string readCpuPossible() {
  const std::string ERROR = "ERROR";
  return readFile("/sys/devices/system/cpu/possible", ERROR);
}
int readCpuIndexMax() {
  constexpr int ERROR = -1;
  return readFile("/sys/devices/system/cpu/kernel_max", ERROR);
}
int64_t readCpuFreqMax(int cpuIndex) {
  const std::string fileName =
    "/sys/devices/system/cpu/cpu" +
    std::to_string(cpuIndex) +
    "/cpufreq/cpuinfo_max_freq";
  constexpr int64_t ERROR = -1;
  return readFile(fileName, ERROR);
}

// returns number of errors
int readHardware(std::vector<std::string>& result, ProtoErrors& errors) {
  std::ifstream f("/proc/cpuinfo");
  if (f.fail()){
    errors.set_hardware("Could not read.");
    return 1;
  }
  const std::string FIELD_KEY = "Hardware\t: ";
  std::string line;
  while (std::getline(f, line)) {
    if (::string_util::startsWith(line, FIELD_KEY)) {
      std::string val = line.substr(FIELD_KEY.length(), std::string::npos);
      result.push_back(val);
    }
  }
  return 0;
}

// returns number of errors
int readFeatures(std::set<std::string>& result, ProtoErrors& errors) {
  std::ifstream f("/proc/cpuinfo");
  if (f.fail()){
    errors.set_features("Could not read.");
    return 1;
  }
  const std::string FIELD_KEY = "Features\t: ";
  std::string line;
  while (std::getline(f, line)) {
    if (::string_util::startsWith(line, FIELD_KEY)) {
      std::string features = line.substr(FIELD_KEY.length(), std::string::npos);
      ::string_util::splitAdd(features, ' ', &result);
    }
  }
  return 0;
}

std::string getSystemPropViaGet(const char* key, ::ProtoErrors& errors) {
  char buffer[PROP_VALUE_MAX + 1];  // +1 for terminator
  int bufferLen = __system_property_get(key, buffer);
  if (bufferLen > PROP_VALUE_MAX){
    const std::string HEADER = "Overflow: ";
    errors.add_system_props(HEADER + key);
    return "";
  }
  return std::string(buffer, bufferLen);
}

std::string getSystemPropViaReadCallback(const char* key) {
  const prop_info* pi = __system_property_find(key);
  if (pi == nullptr) {
    return "";
  }
  std::string result;
  __system_property_read_callback(pi,
    [](void* cookie, const char*, const char* value, unsigned) {
      auto sysOut = reinterpret_cast<std::string*>(cookie);
      *sysOut = value;
    },
    &result
  );
  return result;
}

std::string getSystemProp(const char* key,
  ::ProtoErrors& errors, bool useCallbackApi) {
  if (useCallbackApi) {
    return getSystemPropViaReadCallback(key);
  } else {
    return getSystemPropViaGet(key, errors);
  }
}

// returns number of errors
int addSystemProperties(::ProtoInfo& info, ::ProtoErrors& errors) {
  std::string sdkVersionString =
    getSystemPropViaGet("ro.build.version.sdk", errors);
  info.set_ro_build_version_sdk(sdkVersionString);

  int sdkVersion = atoi(sdkVersionString.c_str());
  bool useCallbackApi = (26 <= sdkVersion);
  info.set_ro_chipname(
    getSystemProp("ro.chipname", errors, useCallbackApi));
  info.set_ro_board_platform(
    getSystemProp("ro.board.platform", errors, useCallbackApi));
  info.set_ro_product_board(
    getSystemProp("ro.product.board", errors, useCallbackApi));
  info.set_ro_mediatek_platform(
    getSystemProp("ro.mediatek.platform", errors, useCallbackApi));
  info.set_ro_arch(
    getSystemProp("ro.arch", errors, useCallbackApi));
  info.set_ro_build_fingerprint(
    getSystemProp("ro.build.fingerprint", errors, useCallbackApi));

  return errors.system_props_size();
}

// returns number of errors
int checkEglError(const char* title, ::ProtoErrors& errors) {
  EGLint error = eglGetError();
  if (error == EGL_SUCCESS) return 0;
  std::stringstream ss;
  ss << title << ": 0x" << std::hex << (int)error << std::dec;
  errors.set_egl(ss.str());
  return 1;
}

int flushGlErrors(::ProtoErrors& errors) {
  int numErrors = 0;
  while (GLenum e = glGetError() != GL_NO_ERROR) {
    std::stringstream ss;
    ss << "0x" << std::hex << (int)e << std::dec;
    errors.add_gl(ss.str());
    numErrors++;
  }
  return numErrors;
}

// returns number of errors
int setupEGl(::ProtoInfoWithErrors& proto) {
  ProtoErrors& errors = *proto.mutable_errors();

  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (int numErrors = checkEglError("eglGetDisplay", errors)){
    return numErrors;
  }

  eglInitialize(display, nullptr, nullptr);  // do not care about egl version
  if (int numErrors = checkEglError("eglInitialize", errors)){
    return numErrors;
  }

  EGLint configAttribs[] = {
    EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    EGL_RED_SIZE, 8,
    EGL_GREEN_SIZE, 8,
    EGL_BLUE_SIZE, 8,
    EGL_ALPHA_SIZE, 8,
    EGL_NONE
  };
  EGLConfig config;
  EGLint numConfigs = -1;
  eglChooseConfig(display, configAttribs, &config, 1, &numConfigs);
  if (int numErrors = checkEglError("eglChooseConfig", errors)){
    return numErrors;
  }

  EGLint contextAttribs[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_NONE
  };
  EGLContext context =
    eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
  if (int numErrors = checkEglError("eglCreateContext", errors)){
    return numErrors;
  }

  EGLint pbufferAttribs[] = {
    EGL_WIDTH,  VIEW_WIDTH,
    EGL_HEIGHT, VIEW_HEIGHT,
    EGL_NONE
  };
  EGLSurface surface = eglCreatePbufferSurface(display, config, pbufferAttribs);
  if (int numErrors = checkEglError("eglCreatePbufferSurface", errors)){
    return numErrors;
  }

  eglMakeCurrent(display, surface, surface, context);
  if (int numErrors = checkEglError("eglMakeCurrent", errors)){
    return numErrors;
  }

  return 0;
}

namespace gl_util {
typedef const GLubyte* GlStr;
typedef GlStr(*FuncTypeGlGetstringi)(GLenum, GLint);
FuncTypeGlGetstringi glGetStringi = 0;
const char* getStringIndexed(GLenum e, GLuint index){
  return reinterpret_cast<const char*>(::gl_util::glGetStringi(e, index));
}

typedef void(*FuncTypeGlGetInteger64v)(GLenum, GLint64*);
FuncTypeGlGetInteger64v glGetInteger64v = 0;
GLint64 getInt64(GLenum e) {
  GLint64 result = -1;
  glGetInteger64v(e, &result);
  return result;
}

typedef void(*FuncTypeGlGetIntegeri_v)(GLenum, GLuint, GLint*);
FuncTypeGlGetIntegeri_v glGetIntegeri_v = 0;
GLint getIntIndexed(GLenum e, GLuint index) {
  GLint result = -1;
  glGetIntegeri_v(e, index, &result);
  return result;
}

const char* getString(GLenum e){
  return reinterpret_cast<const char*>(glGetString(e));
}

GLfloat getFloat(GLenum e) {
  GLfloat result = -1;
  glGetFloatv(e, &result);
  return result;
}
GLint getInt(GLenum e) {
  GLint result = -1;
  glGetIntegerv(e, &result);
  return result;
}
GLboolean getBool(GLenum e) {
  GLboolean result = false;
  glGetBooleanv(e, &result);
  return result;
}
}  // namespace gl_util

void addGlConstsV2_0(::ProtoGl& gl) {
  gl.set_gl_aliased_line_width_range(
    ::gl_util::getFloat(GL_ALIASED_LINE_WIDTH_RANGE));
  gl.set_gl_aliased_point_size_range(
    ::gl_util::getFloat(GL_ALIASED_POINT_SIZE_RANGE));
  gl.set_gl_max_combined_texture_image_units(
    ::gl_util::getInt(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_cube_map_texture_size(
    ::gl_util::getInt(GL_MAX_CUBE_MAP_TEXTURE_SIZE));
  gl.set_gl_max_fragment_uniform_vectors(
    ::gl_util::getInt(GL_MAX_FRAGMENT_UNIFORM_VECTORS));
  gl.set_gl_max_renderbuffer_size(
    ::gl_util::getInt(GL_MAX_RENDERBUFFER_SIZE));
  gl.set_gl_max_texture_image_units(
    ::gl_util::getInt(GL_MAX_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_texture_size(
    ::gl_util::getInt(GL_MAX_TEXTURE_SIZE));
  gl.set_gl_max_varying_vectors(
    ::gl_util::getInt(GL_MAX_VARYING_VECTORS));
  gl.set_gl_max_vertex_attribs(
    ::gl_util::getInt(GL_MAX_VERTEX_ATTRIBS));
  gl.set_gl_max_vertex_texture_image_units(
    ::gl_util::getInt(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_vertex_uniform_vectors(
    ::gl_util::getInt(GL_MAX_VERTEX_UNIFORM_VECTORS));
  gl.set_gl_max_viewport_dims(
    ::gl_util::getInt(GL_MAX_VIEWPORT_DIMS));
  gl.set_gl_shader_compiler(
    ::gl_util::getBool(GL_SHADER_COMPILER));
  gl.set_gl_subpixel_bits(
    ::gl_util::getInt(GL_SUBPIXEL_BITS));

  GLint numCompressedFormats =
    ::gl_util::getInt(GL_NUM_COMPRESSED_TEXTURE_FORMATS);
  gl.set_gl_num_compressed_texture_formats(numCompressedFormats);
  std::vector<GLint> compressedFormats(numCompressedFormats);
  glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, &compressedFormats[0]);
  for (auto x : compressedFormats) {
    gl.add_gl_compressed_texture_formats(x);
  }

  GLint numShaderBinFormats = ::gl_util::getInt(GL_NUM_SHADER_BINARY_FORMATS);
  gl.set_gl_num_shader_binary_formats(numShaderBinFormats);
  std::vector<GLint> shaderBinFormats(numShaderBinFormats);
  glGetIntegerv(GL_SHADER_BINARY_FORMATS, &shaderBinFormats[0]);
  for (auto x : shaderBinFormats) {
    gl.add_gl_shader_binary_formats(x);
  }

  // shader precision formats
  GLint spfr = -1;  // range
  GLint spfp = -1;  // precision
  glGetShaderPrecisionFormat(GL_VERTEX_SHADER, GL_LOW_FLOAT, &spfr, &spfp);
  gl.set_spf_vertex_float_low_range(spfr);
  gl.set_spf_vertex_float_low_prec(spfp);
  glGetShaderPrecisionFormat(GL_VERTEX_SHADER, GL_MEDIUM_FLOAT, &spfr, &spfp);
  gl.set_spf_vertex_float_med_range(spfr);
  gl.set_spf_vertex_float_med_prec(spfp);
  glGetShaderPrecisionFormat(GL_VERTEX_SHADER, GL_HIGH_FLOAT, &spfr, &spfp);
  gl.set_spf_vertex_float_hig_range(spfr);
  gl.set_spf_vertex_float_hig_prec(spfp);
  glGetShaderPrecisionFormat(GL_VERTEX_SHADER, GL_LOW_INT, &spfr, &spfp);
  gl.set_spf_vertex_int_low_range(spfr);
  gl.set_spf_vertex_int_low_prec(spfp);
  glGetShaderPrecisionFormat(GL_VERTEX_SHADER, GL_MEDIUM_INT, &spfr, &spfp);
  gl.set_spf_vertex_int_med_range(spfr);
  gl.set_spf_vertex_int_med_prec(spfp);
  glGetShaderPrecisionFormat(GL_VERTEX_SHADER, GL_HIGH_INT, &spfr, &spfp);
  gl.set_spf_vertex_int_hig_range(spfr);
  gl.set_spf_vertex_int_hig_prec(spfp);
  glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_LOW_FLOAT, &spfr, &spfp);
  gl.set_spf_fragment_float_low_range(spfr);
  gl.set_spf_fragment_float_low_prec(spfp);
  glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT, &spfr, &spfp);
  gl.set_spf_fragment_float_med_range(spfr);
  gl.set_spf_fragment_float_med_prec(spfp);
  glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, &spfr, &spfp);
  gl.set_spf_fragment_float_hig_range(spfr);
  gl.set_spf_fragment_float_hig_prec(spfp);
  glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_LOW_INT, &spfr, &spfp);
  gl.set_spf_fragment_int_low_range(spfr);
  gl.set_spf_fragment_int_low_prec(spfp);
  glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_MEDIUM_INT, &spfr, &spfp);
  gl.set_spf_fragment_int_med_range(spfr);
  gl.set_spf_fragment_int_med_prec(spfp);
  glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_INT, &spfr, &spfp);
  gl.set_spf_fragment_int_hig_range(spfr);
  gl.set_spf_fragment_int_hig_prec(spfp);
}
void addGlConstsV3_0(::ProtoGl& gl) {
  gl.set_gl_max_3d_texture_size(
    ::gl_util::getInt(GL_MAX_3D_TEXTURE_SIZE));
  gl.set_gl_max_array_texture_layers(
    ::gl_util::getInt(GL_MAX_ARRAY_TEXTURE_LAYERS));
  gl.set_gl_max_color_attachments(
    ::gl_util::getInt(GL_MAX_COLOR_ATTACHMENTS));
  gl.set_gl_max_combined_uniform_blocks(
    ::gl_util::getInt(GL_MAX_COMBINED_UNIFORM_BLOCKS));
  gl.set_gl_max_draw_buffers(
    ::gl_util::getInt(GL_MAX_DRAW_BUFFERS));
  gl.set_gl_max_elements_indices(
    ::gl_util::getInt(GL_MAX_ELEMENTS_INDICES));
  gl.set_gl_max_elements_vertices(
    ::gl_util::getInt(GL_MAX_ELEMENTS_VERTICES));
  gl.set_gl_max_fragment_input_components(
    ::gl_util::getInt(GL_MAX_FRAGMENT_INPUT_COMPONENTS));
  gl.set_gl_max_fragment_uniform_blocks(
    ::gl_util::getInt(GL_MAX_FRAGMENT_UNIFORM_BLOCKS));
  gl.set_gl_max_fragment_uniform_components(
    ::gl_util::getInt(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS));
  gl.set_gl_max_program_texel_offset(
    ::gl_util::getInt(GL_MAX_PROGRAM_TEXEL_OFFSET));
  gl.set_gl_max_transform_feedback_interleaved_components(
    ::gl_util::getInt(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS));
  gl.set_gl_max_transform_feedback_separate_attribs(
    ::gl_util::getInt(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS));
  gl.set_gl_max_transform_feedback_separate_components(
    ::gl_util::getInt(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS));
  gl.set_gl_max_uniform_buffer_bindings(
    ::gl_util::getInt(GL_MAX_UNIFORM_BUFFER_BINDINGS));
  gl.set_gl_max_varying_components(
    ::gl_util::getInt(GL_MAX_VARYING_COMPONENTS));
  gl.set_gl_max_vertex_output_components(
    ::gl_util::getInt(GL_MAX_VERTEX_OUTPUT_COMPONENTS));
  gl.set_gl_max_vertex_uniform_blocks(
    ::gl_util::getInt(GL_MAX_VERTEX_UNIFORM_BLOCKS));
  gl.set_gl_max_vertex_uniform_components(
    ::gl_util::getInt(GL_MAX_VERTEX_UNIFORM_COMPONENTS));
  gl.set_gl_min_program_texel_offset(
    ::gl_util::getInt(GL_MIN_PROGRAM_TEXEL_OFFSET));
  gl.set_gl_uniform_buffer_offset_alignment(
    ::gl_util::getInt(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT));
  gl.set_gl_max_samples(
    ::gl_util::getInt(GL_MAX_SAMPLES));

  gl.set_gl_max_texture_lod_bias(::gl_util::getFloat(GL_MAX_TEXTURE_LOD_BIAS));

  ::gl_util::glGetInteger64v =
    reinterpret_cast<::gl_util::FuncTypeGlGetInteger64v>(
      eglGetProcAddress("glGetInteger64v"));
  gl.set_gl_max_combined_fragment_uniform_components(
    ::gl_util::getInt64(GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS));
  gl.set_gl_max_element_index(
    ::gl_util::getInt64(GL_MAX_ELEMENT_INDEX));
  gl.set_gl_max_server_wait_timeout(
    ::gl_util::getInt64(GL_MAX_SERVER_WAIT_TIMEOUT));
  gl.set_gl_max_uniform_block_size(
    ::gl_util::getInt64(GL_MAX_UNIFORM_BLOCK_SIZE));
}
void addGlConstsV3_1(::ProtoGl& gl) {
  gl.set_gl_max_atomic_counter_buffer_bindings(
    ::gl_util::getInt(GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS));
  gl.set_gl_max_atomic_counter_buffer_size(
    ::gl_util::getInt(GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE));
  gl.set_gl_max_color_texture_samples(
    ::gl_util::getInt(GL_MAX_COLOR_TEXTURE_SAMPLES));
  gl.set_gl_max_combined_atomic_counters(
    ::gl_util::getInt(GL_MAX_COMBINED_ATOMIC_COUNTERS));
  gl.set_gl_max_combined_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_combined_compute_uniform_components(
    ::gl_util::getInt(GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS));
  gl.set_gl_max_combined_image_uniforms(
    ::gl_util::getInt(GL_MAX_COMBINED_IMAGE_UNIFORMS));
  gl.set_gl_max_combined_shader_output_resources(
    ::gl_util::getInt(GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES));
  gl.set_gl_max_combined_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS));
  gl.set_gl_max_compute_atomic_counters(
    ::gl_util::getInt(GL_MAX_COMPUTE_ATOMIC_COUNTERS));
  gl.set_gl_max_compute_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_compute_image_uniforms(
    ::gl_util::getInt(GL_MAX_COMPUTE_IMAGE_UNIFORMS));
  gl.set_gl_max_compute_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS));
  gl.set_gl_max_compute_shared_memory_size(
    ::gl_util::getInt(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE));
  gl.set_gl_max_compute_texture_image_units(
    ::gl_util::getInt(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_compute_uniform_blocks(
    ::gl_util::getInt(GL_MAX_COMPUTE_UNIFORM_BLOCKS));
  gl.set_gl_max_compute_uniform_components(
    ::gl_util::getInt(GL_MAX_COMPUTE_UNIFORM_COMPONENTS));
  gl.set_gl_max_compute_work_group_invocations(
    ::gl_util::getInt(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS));
  gl.set_gl_max_depth_texture_samples(
    ::gl_util::getInt(GL_MAX_DEPTH_TEXTURE_SAMPLES));
  gl.set_gl_max_fragment_atomic_counters(
    ::gl_util::getInt(GL_MAX_FRAGMENT_ATOMIC_COUNTERS));
  gl.set_gl_max_fragment_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_fragment_image_uniforms(
    ::gl_util::getInt(GL_MAX_FRAGMENT_IMAGE_UNIFORMS));
  gl.set_gl_max_fragment_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS));
  gl.set_gl_max_framebuffer_height(
    ::gl_util::getInt(GL_MAX_FRAMEBUFFER_HEIGHT));
  gl.set_gl_max_framebuffer_samples(
    ::gl_util::getInt(GL_MAX_FRAMEBUFFER_SAMPLES));
  gl.set_gl_max_framebuffer_width(
    ::gl_util::getInt(GL_MAX_FRAMEBUFFER_WIDTH));
  gl.set_gl_max_image_units(
    ::gl_util::getInt(GL_MAX_IMAGE_UNITS));
  gl.set_gl_max_integer_samples(
    ::gl_util::getInt(GL_MAX_INTEGER_SAMPLES));
  gl.set_gl_max_program_texture_gather_offset(
    ::gl_util::getInt(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET));
  gl.set_gl_max_sample_mask_words(
    ::gl_util::getInt(GL_MAX_SAMPLE_MASK_WORDS));
  gl.set_gl_max_shader_storage_buffer_bindings(
    ::gl_util::getInt(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS));
  gl.set_gl_max_uniform_locations(
    ::gl_util::getInt(GL_MAX_UNIFORM_LOCATIONS));
  gl.set_gl_max_vertex_atomic_counters(
    ::gl_util::getInt(GL_MAX_VERTEX_ATOMIC_COUNTERS));
  gl.set_gl_max_vertex_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_vertex_attrib_bindings(
    ::gl_util::getInt(GL_MAX_VERTEX_ATTRIB_BINDINGS));
  gl.set_gl_max_vertex_attrib_relative_offset(
    ::gl_util::getInt(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET));
  gl.set_gl_max_vertex_attrib_stride(
    ::gl_util::getInt(GL_MAX_VERTEX_ATTRIB_STRIDE));
  gl.set_gl_max_vertex_image_uniforms(
    ::gl_util::getInt(GL_MAX_VERTEX_IMAGE_UNIFORMS));
  gl.set_gl_max_vertex_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS));
  gl.set_gl_min_program_texture_gather_offset(
    ::gl_util::getInt(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET));
  gl.set_gl_shader_storage_buffer_offset_alignment(
    ::gl_util::getInt(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT));

  gl.set_gl_max_shader_storage_block_size(
    ::gl_util::getInt64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE));

  ::gl_util::glGetIntegeri_v =
    reinterpret_cast<::gl_util::FuncTypeGlGetIntegeri_v>(
      eglGetProcAddress("glGetIntegeri_v"));
  gl.set_gl_max_compute_work_group_count_0(
    ::gl_util::getIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0));
  gl.set_gl_max_compute_work_group_count_1(
    ::gl_util::getIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1));
  gl.set_gl_max_compute_work_group_count_2(
    ::gl_util::getIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2));
  gl.set_gl_max_compute_work_group_size_0(
    ::gl_util::getIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0));
  gl.set_gl_max_compute_work_group_size_1(
    ::gl_util::getIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1));
  gl.set_gl_max_compute_work_group_size_2(
    ::gl_util::getIntIndexed(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2));
}
void addGlConstsV3_2(::ProtoGl& gl) {
  gl.set_gl_context_flags(
    ::gl_util::getInt(GL_CONTEXT_FLAGS));
  gl.set_gl_fragment_interpolation_offset_bits(
    ::gl_util::getInt(GL_FRAGMENT_INTERPOLATION_OFFSET_BITS));
  gl.set_gl_layer_provoking_vertex(
    ::gl_util::getInt(GL_LAYER_PROVOKING_VERTEX));
  gl.set_gl_max_combined_geometry_uniform_components(
    ::gl_util::getInt(GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS));
  gl.set_gl_max_combined_tess_control_uniform_components(
    ::gl_util::getInt(GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS));
  gl.set_gl_max_combined_tess_evaluation_uniform_components(
    ::gl_util::getInt(GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS));
  gl.set_gl_max_debug_group_stack_depth(
    ::gl_util::getInt(GL_MAX_DEBUG_GROUP_STACK_DEPTH));
  gl.set_gl_max_debug_logged_messages(
    ::gl_util::getInt(GL_MAX_DEBUG_LOGGED_MESSAGES));
  gl.set_gl_max_debug_message_length(
    ::gl_util::getInt(GL_MAX_DEBUG_MESSAGE_LENGTH));
  gl.set_gl_max_framebuffer_layers(
    ::gl_util::getInt(GL_MAX_FRAMEBUFFER_LAYERS));
  gl.set_gl_max_geometry_atomic_counters(
    ::gl_util::getInt(GL_MAX_GEOMETRY_ATOMIC_COUNTERS));
  gl.set_gl_max_geometry_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_geometry_image_uniforms(
    ::gl_util::getInt(GL_MAX_GEOMETRY_IMAGE_UNIFORMS));
  gl.set_gl_max_geometry_input_components(
    ::gl_util::getInt(GL_MAX_GEOMETRY_INPUT_COMPONENTS));
  gl.set_gl_max_geometry_output_components(
    ::gl_util::getInt(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS));
  gl.set_gl_max_geometry_output_vertices(
    ::gl_util::getInt(GL_MAX_GEOMETRY_OUTPUT_VERTICES));
  gl.set_gl_max_geometry_shader_invocations(
    ::gl_util::getInt(GL_MAX_GEOMETRY_SHADER_INVOCATIONS));
  gl.set_gl_max_geometry_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS));
  gl.set_gl_max_geometry_texture_image_units(
    ::gl_util::getInt(GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_geometry_total_output_components(
    ::gl_util::getInt(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS));
  gl.set_gl_max_geometry_uniform_blocks(
    ::gl_util::getInt(GL_MAX_GEOMETRY_UNIFORM_BLOCKS));
  gl.set_gl_max_geometry_uniform_components(
    ::gl_util::getInt(GL_MAX_GEOMETRY_UNIFORM_COMPONENTS));
  gl.set_gl_max_label_length(
    ::gl_util::getInt(GL_MAX_LABEL_LENGTH));
  gl.set_gl_max_patch_vertices(
    ::gl_util::getInt(GL_MAX_PATCH_VERTICES));
  gl.set_gl_max_tess_control_atomic_counters(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS));
  gl.set_gl_max_tess_control_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_tess_control_image_uniforms(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS));
  gl.set_gl_max_tess_control_input_components(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_INPUT_COMPONENTS));
  gl.set_gl_max_tess_control_output_components(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS));
  gl.set_gl_max_tess_control_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS));
  gl.set_gl_max_tess_control_texture_image_units(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_tess_control_total_output_components(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS));
  gl.set_gl_max_tess_control_uniform_blocks(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS));
  gl.set_gl_max_tess_control_uniform_components(
    ::gl_util::getInt(GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS));
  gl.set_gl_max_tess_evaluation_atomic_counters(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS));
  gl.set_gl_max_tess_evaluation_atomic_counter_buffers(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS));
  gl.set_gl_max_tess_evaluation_image_uniforms(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS));
  gl.set_gl_max_tess_evaluation_input_components(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS));
  gl.set_gl_max_tess_evaluation_output_components(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS));
  gl.set_gl_max_tess_evaluation_shader_storage_blocks(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS));
  gl.set_gl_max_tess_evaluation_texture_image_units(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS));
  gl.set_gl_max_tess_evaluation_uniform_blocks(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS));
  gl.set_gl_max_tess_evaluation_uniform_components(
    ::gl_util::getInt(GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS));
  gl.set_gl_max_tess_gen_level(
    ::gl_util::getInt(GL_MAX_TESS_GEN_LEVEL));
  gl.set_gl_max_tess_patch_components(
    ::gl_util::getInt(GL_MAX_TESS_PATCH_COMPONENTS));
  gl.set_gl_max_texture_buffer_size(
    ::gl_util::getInt(GL_MAX_TEXTURE_BUFFER_SIZE));
  gl.set_gl_texture_buffer_offset_alignment(
    ::gl_util::getInt(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT));
  gl.set_gl_reset_notification_strategy(
    ::gl_util::getInt(GL_RESET_NOTIFICATION_STRATEGY));
  gl.set_gl_max_fragment_interpolation_offset(
    ::gl_util::getFloat(GL_MAX_FRAGMENT_INTERPOLATION_OFFSET));
  gl.set_gl_min_fragment_interpolation_offset(
    ::gl_util::getFloat(GL_MIN_FRAGMENT_INTERPOLATION_OFFSET));
  gl.set_gl_multisample_line_width_granularity(
    ::gl_util::getFloat(GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY));
  gl.set_gl_multisample_line_width_range(
    ::gl_util::getFloat(GL_MULTISAMPLE_LINE_WIDTH_RANGE));

  gl.set_gl_primitive_restart_for_patches_supported(
    ::gl_util::getBool(GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED));
}

// returns number of errors
int addGl(::ProtoInfoWithErrors& proto) {
  int numErrors = 0;

  ::ProtoInfo& info = *proto.mutable_info();
  ::ProtoGl& gl = *info.mutable_gl();
  ::ProtoErrors& errors = *proto.mutable_errors();

  gl.set_renderer(::gl_util::getString(GL_RENDERER));
  gl.set_vendor(::gl_util::getString(GL_VENDOR));
  gl.set_version(::gl_util::getString(GL_VERSION));
  gl.set_shading_language_version(
    ::gl_util::getString(GL_SHADING_LANGUAGE_VERSION));

  numErrors += flushGlErrors(errors);

  GLint glVerMajor = -1;
  GLint glVerMinor = -1;
  glGetIntegerv(GL_MAJOR_VERSION, &glVerMajor);
  // if GL_MAJOR_VERSION is not recognized, assume version 2.0
  if (glGetError() != GL_NO_ERROR) {
    glVerMajor = 2;
    glVerMinor = 0;
  } else {
    glGetIntegerv(GL_MINOR_VERSION, &glVerMinor);
  }
  gl.set_version_major(glVerMajor);
  gl.set_version_minor(glVerMinor);

  // gl extensions
  if (glVerMajor >= 3) {
    int numExts = -1;
    glGetIntegerv(GL_NUM_EXTENSIONS, &numExts);
    ::gl_util::glGetStringi = reinterpret_cast<::gl_util::FuncTypeGlGetstringi>(
                                eglGetProcAddress("glGetStringi"));
    for (int i = 0; i < numExts; i++) {
      std::string s = ::gl_util::getStringIndexed(GL_EXTENSIONS, i);
      gl.add_extension(s);
    }
  } else {
    std::string exts = ::gl_util::getString(GL_EXTENSIONS);
    std::set<std::string> split;
    ::string_util::splitAdd(exts, ' ', &split);
    for (const std::string& s : split) {
      gl.add_extension(s);
    }
  }

  if (glVerMajor > 2 || (glVerMajor == 2 && glVerMinor >= 0)) {  // >= gles 2.0
    addGlConstsV2_0(gl);
  }
  if (glVerMajor > 3 || (glVerMajor == 3 && glVerMinor >= 0)) {  // >= gles 3.0
    addGlConstsV3_0(gl);
  }
  if (glVerMajor > 3 || (glVerMajor == 3 && glVerMinor >= 1)) {  // >= gles 3.1
    addGlConstsV3_1(gl);
  }
  if (glVerMajor > 3 || (glVerMajor == 3 && glVerMinor >= 2)) {  // >= gles 3.2
    addGlConstsV3_2(gl);
  }

  numErrors += flushGlErrors(errors);
  return numErrors;
}
}  // namespace

namespace androidgamesdk_deviceinfo {
int createProto(::ProtoInfoWithErrors& proto) {
  int numErrors = 0;

  ProtoInfo& info = *proto.mutable_info();
  info.set_version(1);

  int cpuIndexMax = readCpuIndexMax();
  info.set_cpu_max_index(cpuIndexMax);

  for (int cpuIndex = 0; cpuIndex <= cpuIndexMax; cpuIndex++) {
    ProtoCpuCore* newCore = info.add_cpu_core();
    int64_t freqMax = readCpuFreqMax(cpuIndex);
    if (freqMax > 0) {
      newCore->set_freq_max(freqMax);
    }
  }

  info.set_cpu_present(readCpuPresent());
  info.set_cpu_possible(readCpuPossible());

  ProtoErrors& errors = *proto.mutable_errors();

  std::vector<std::string> hardware;
  numErrors += readHardware(hardware, errors);
  for (const std::string& s : hardware) {
    info.add_hardware(s);
  }

  std::set<std::string> features;
  numErrors += readFeatures(features, errors);
  for (const std::string& s : features) {
    info.add_cpu_extension(s);
  }

  numErrors += addSystemProperties(info, errors);

  int numErrorsEgl = setupEGl(proto);
  numErrors += numErrorsEgl;
  if (numErrorsEgl == 0) {
    numErrors += addGl(proto);
  }

  return numErrors;
}
}  // namespace androidgamesdk_deviceinfo

#include <jni.h>

extern "C" {
JNIEXPORT jbyteArray JNICALL
Java_com_google_androidgamesdk_DeviceInfoJni_getProtoSerialized(
                                        JNIEnv *env, jobject) {
  androidgamesdk_deviceinfo::InfoWithErrors proto;
  androidgamesdk_deviceinfo::createProto(proto);

  size_t bufferSize = proto.ByteSize();
  void* buffer = malloc(bufferSize);
  proto.SerializeToArray(buffer, bufferSize);
  jbyteArray result = env->NewByteArray(bufferSize);
  env->SetByteArrayRegion(result, 0, bufferSize, static_cast<jbyte*>(buffer));
  free(buffer);
  return result;
}
}  // extern "C"