#include <cstring>
#include <vector>

#include <webglcontext/include/webgl.h>
#include <canvascontext/include/imageData-context.h>
// #include <node.h>

/* #include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "glesjs", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "glesjs", __VA_ARGS__)) */

// using namespace node;
using namespace v8;
using namespace std;

#if defined(ANDROID) || defined(LUMIN)
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR glFramebufferTextureMultiviewOVRExt;
PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVR glFramebufferTextureMultisampleMultiviewOVRExt;

bool extensionFunctionsInitialized = false;
#endif

// forward declarations
/* enum GLObjectType {
  GLOBJECT_TYPE_BUFFER,
  GLOBJECT_TYPE_FRAMEBUFFER,
  GLOBJECT_TYPE_PROGRAM,
  GLOBJECT_TYPE_RENDERBUFFER,
  GLOBJECT_TYPE_SHADER,
  GLOBJECT_TYPE_TEXTURE,
};

void registerGLObj(GLObjectType type, GLuint obj);
void unregisterGLObj(GLuint obj); */

// WebGLRenderingContext

// Used to be a macro, hence the uppercase name.
#define JS_GL_SET_CONSTANT(name, constant) proto->Set(JS_STR( name ), JS_INT(constant))

#define JS_GL_CONSTANT(name) JS_GL_SET_CONSTANT(#name, GL_ ## name)

GlShader::~GlShader() {}

template<NAN_METHOD(F)>
NAN_METHOD(glCallWrap) {
  Local<Object> glObj = info.This();
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(glObj);
  if (gl->live) {
    if (gl->windowHandle) {
      windowsystem::SetCurrentWindowContext(gl->windowHandle);
    }

    F(info);
  } else {
    info.GetReturnValue().Set(JS_STR(""));
  }
}
template<NAN_GETTER(F)>
NAN_GETTER(glGetterWrap) {
  Local<Object> glObj = info.This();
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(glObj);
  if (gl->live) {
    if (gl->windowHandle) {
      windowsystem::SetCurrentWindowContext(gl->windowHandle);
    }

    F(property, info);
  } else {
    info.GetReturnValue().Set(JS_STR(""));
  }
}

template <typename T>
void setGlConstants(T &proto) {
  // OpenGL ES 2.1 constants

   //---------------------------------------------------------------------------
  /* ClearBufferMask */
  JS_GL_CONSTANT(DEPTH_BUFFER_BIT);
  JS_GL_CONSTANT(STENCIL_BUFFER_BIT);
  JS_GL_CONSTANT(COLOR_BUFFER_BIT);

  /* Boolean */
  JS_GL_CONSTANT(FALSE);
  JS_GL_CONSTANT(TRUE);

  /* BeginMode */
  JS_GL_CONSTANT(POINTS);
  JS_GL_CONSTANT(LINES);
  JS_GL_CONSTANT(LINE_LOOP);
  JS_GL_CONSTANT(LINE_STRIP);
  JS_GL_CONSTANT(TRIANGLES);
  JS_GL_CONSTANT(TRIANGLE_STRIP);
  JS_GL_CONSTANT(TRIANGLE_FAN);

  /* AlphaFunction (not supported in ES20) */
  /*      GL_NEVER */
  /*      GL_LESS */
  /*      GL_EQUAL */
  /*      GL_LEQUAL */
  /*      GL_GREATER */
  /*      GL_NOTEQUAL */
  /*      GL_GEQUAL */
  /*      GL_ALWAYS */

  /* BlendingFactorDest */
  JS_GL_CONSTANT(ZERO);
  JS_GL_CONSTANT(ONE);
  JS_GL_CONSTANT(SRC_COLOR);
  JS_GL_CONSTANT(ONE_MINUS_SRC_COLOR);
  JS_GL_CONSTANT(SRC_ALPHA);
  JS_GL_CONSTANT(ONE_MINUS_SRC_ALPHA);
  JS_GL_CONSTANT(DST_ALPHA);
  JS_GL_CONSTANT(ONE_MINUS_DST_ALPHA);

  /* BlendingFactorSrc */
  /*      GL_ZERO */
  /*      GL_ONE */
  JS_GL_CONSTANT(DST_COLOR);
  JS_GL_CONSTANT(ONE_MINUS_DST_COLOR);
  JS_GL_CONSTANT(SRC_ALPHA_SATURATE);
  /*      GL_SRC_ALPHA */
  /*      GL_ONE_MINUS_SRC_ALPHA */
  /*      GL_DST_ALPHA */
  /*      GL_ONE_MINUS_DST_ALPHA */

  /* BlendEquationSeparate */
  JS_GL_CONSTANT(FUNC_ADD);
  JS_GL_CONSTANT(BLEND_EQUATION);
  JS_GL_CONSTANT(BLEND_EQUATION_RGB);    /* same as BLEND_EQUATION */
  JS_GL_CONSTANT(BLEND_EQUATION_ALPHA);

  /* BlendSubtract */
  JS_GL_CONSTANT(FUNC_SUBTRACT);
  JS_GL_CONSTANT(FUNC_REVERSE_SUBTRACT);

  /* Separate Blend Functions */
  JS_GL_CONSTANT(BLEND_DST_RGB);
  JS_GL_CONSTANT(BLEND_SRC_RGB);
  JS_GL_CONSTANT(BLEND_DST_ALPHA);
  JS_GL_CONSTANT(BLEND_SRC_ALPHA);
  JS_GL_CONSTANT(CONSTANT_COLOR);
  JS_GL_CONSTANT(ONE_MINUS_CONSTANT_COLOR);
  JS_GL_CONSTANT(CONSTANT_ALPHA);
  JS_GL_CONSTANT(ONE_MINUS_CONSTANT_ALPHA);
  JS_GL_CONSTANT(BLEND_COLOR);

  /* Buffer Objects */
  JS_GL_CONSTANT(ARRAY_BUFFER);
  JS_GL_CONSTANT(ELEMENT_ARRAY_BUFFER);
  JS_GL_CONSTANT(ARRAY_BUFFER_BINDING);
  JS_GL_CONSTANT(ELEMENT_ARRAY_BUFFER_BINDING);
  JS_GL_CONSTANT(COPY_READ_BUFFER);
  JS_GL_CONSTANT(COPY_WRITE_BUFFER);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BUFFER);
  JS_GL_CONSTANT(UNIFORM_BUFFER);
  JS_GL_CONSTANT(PIXEL_PACK_BUFFER);
  JS_GL_CONSTANT(PIXEL_UNPACK_BUFFER);

  JS_GL_CONSTANT(STREAM_DRAW);
  JS_GL_CONSTANT(STATIC_DRAW);
  JS_GL_CONSTANT(DYNAMIC_DRAW);

  JS_GL_CONSTANT(BUFFER_SIZE);
  JS_GL_CONSTANT(BUFFER_USAGE);

  JS_GL_CONSTANT(CURRENT_VERTEX_ATTRIB);

  /* CullFaceMode */
  JS_GL_CONSTANT(FRONT);
  JS_GL_CONSTANT(BACK);
  JS_GL_CONSTANT(FRONT_AND_BACK);

  /* DepthFunction */
  /*      GL_NEVER */
  /*      GL_LESS */
  /*      GL_EQUAL */
  /*      GL_LEQUAL */
  /*      GL_GREATER */
  /*      GL_NOTEQUAL */
  /*      GL_GEQUAL */
  /*      GL_ALWAYS */

  /* EnableCap */
  JS_GL_CONSTANT(TEXTURE_2D);
  JS_GL_CONSTANT(TEXTURE_2D_MULTISAMPLE);
  JS_GL_CONSTANT(CULL_FACE);
  JS_GL_CONSTANT(BLEND);
  JS_GL_CONSTANT(DITHER);
  JS_GL_CONSTANT(STENCIL_TEST);
  JS_GL_CONSTANT(DEPTH_TEST);
  JS_GL_CONSTANT(SCISSOR_TEST);
  JS_GL_CONSTANT(POLYGON_OFFSET_FILL);
  JS_GL_CONSTANT(SAMPLE_ALPHA_TO_COVERAGE);
  JS_GL_CONSTANT(SAMPLE_COVERAGE);

  /* ErrorCode */
  JS_GL_CONSTANT(NO_ERROR);
  JS_GL_CONSTANT(INVALID_ENUM);
  JS_GL_CONSTANT(INVALID_VALUE);
  JS_GL_CONSTANT(INVALID_OPERATION);
  JS_GL_CONSTANT(OUT_OF_MEMORY);

  /* FrontFaceDirection */
  JS_GL_CONSTANT(CW);
  JS_GL_CONSTANT(CCW);

  /* GetPName */
  JS_GL_CONSTANT(LINE_WIDTH);
  JS_GL_CONSTANT(ALIASED_POINT_SIZE_RANGE);
  JS_GL_CONSTANT(ALIASED_LINE_WIDTH_RANGE);
  JS_GL_CONSTANT(CULL_FACE_MODE);
  JS_GL_CONSTANT(FRONT_FACE);
  JS_GL_CONSTANT(DEPTH_RANGE);
  JS_GL_CONSTANT(DEPTH_WRITEMASK);
  JS_GL_CONSTANT(DEPTH_CLEAR_VALUE);
  JS_GL_CONSTANT(DEPTH_FUNC);
  JS_GL_CONSTANT(STENCIL_CLEAR_VALUE);
  JS_GL_CONSTANT(STENCIL_FUNC);
  JS_GL_CONSTANT(STENCIL_FAIL);
  JS_GL_CONSTANT(STENCIL_PASS_DEPTH_FAIL);
  JS_GL_CONSTANT(STENCIL_PASS_DEPTH_PASS);
  JS_GL_CONSTANT(STENCIL_REF);
  JS_GL_CONSTANT(STENCIL_VALUE_MASK);
  JS_GL_CONSTANT(STENCIL_WRITEMASK);
  JS_GL_CONSTANT(STENCIL_BACK_FUNC);
  JS_GL_CONSTANT(STENCIL_BACK_FAIL);
  JS_GL_CONSTANT(STENCIL_BACK_PASS_DEPTH_FAIL);
  JS_GL_CONSTANT(STENCIL_BACK_PASS_DEPTH_PASS);
  JS_GL_CONSTANT(STENCIL_BACK_REF);
  JS_GL_CONSTANT(STENCIL_BACK_VALUE_MASK);
  JS_GL_CONSTANT(STENCIL_BACK_WRITEMASK);
  JS_GL_CONSTANT(VIEWPORT);
  JS_GL_CONSTANT(SCISSOR_BOX);
  /*      GL_SCISSOR_TEST */
  JS_GL_CONSTANT(COLOR_CLEAR_VALUE);
  JS_GL_CONSTANT(COLOR_WRITEMASK);
  JS_GL_CONSTANT(UNPACK_ALIGNMENT);
  JS_GL_CONSTANT(PACK_ALIGNMENT);
  JS_GL_CONSTANT(MAX_TEXTURE_SIZE);
  JS_GL_CONSTANT(MAX_VIEWPORT_DIMS);
  JS_GL_CONSTANT(SUBPIXEL_BITS);
  JS_GL_CONSTANT(RED_BITS);
  JS_GL_CONSTANT(GREEN_BITS);
  JS_GL_CONSTANT(BLUE_BITS);
  JS_GL_CONSTANT(ALPHA_BITS);
  JS_GL_CONSTANT(DEPTH_BITS);
  JS_GL_CONSTANT(STENCIL_BITS);
  JS_GL_CONSTANT(POLYGON_OFFSET_UNITS);
  /*      GL_POLYGON_OFFSET_FILL */
  JS_GL_CONSTANT(POLYGON_OFFSET_FACTOR);
  JS_GL_CONSTANT(TEXTURE_BINDING_2D);
  JS_GL_CONSTANT(SAMPLE_BUFFERS);
  JS_GL_CONSTANT(SAMPLES);
  JS_GL_CONSTANT(SAMPLE_COVERAGE_VALUE);
  JS_GL_CONSTANT(SAMPLE_COVERAGE_INVERT);

  /* GetTextureParameter */
  /*      GL_TEXTURE_MAG_FILTER */
  /*      GL_TEXTURE_MIN_FILTER */
  /*      GL_TEXTURE_WRAP_S */
  /*      GL_TEXTURE_WRAP_T */

  JS_GL_CONSTANT(NUM_COMPRESSED_TEXTURE_FORMATS);
  JS_GL_CONSTANT(COMPRESSED_TEXTURE_FORMATS);

  /* HintMode */
  JS_GL_CONSTANT(DONT_CARE);
  JS_GL_CONSTANT(FASTEST);
  JS_GL_CONSTANT(NICEST);

  /* HintTarget */
  JS_GL_CONSTANT(GENERATE_MIPMAP_HINT);

  /* DataType */
  JS_GL_CONSTANT(BYTE);
  JS_GL_CONSTANT(UNSIGNED_BYTE);
  JS_GL_CONSTANT(SHORT);
  JS_GL_CONSTANT(UNSIGNED_SHORT);
  JS_GL_CONSTANT(INT);
  JS_GL_CONSTANT(UNSIGNED_INT);
  JS_GL_CONSTANT(FLOAT);
// #ifndef __APPLE__
  JS_GL_CONSTANT(FIXED);
// #endif

  /* PixelFormat */
  JS_GL_CONSTANT(DEPTH_COMPONENT);
  JS_GL_CONSTANT(ALPHA);
  JS_GL_CONSTANT(RGB);
  JS_GL_CONSTANT(RGBA);
  JS_GL_CONSTANT(LUMINANCE);
  JS_GL_CONSTANT(LUMINANCE_ALPHA);

  /* PixelType */
  /*      GL_UNSIGNED_BYTE */
  JS_GL_CONSTANT(UNSIGNED_SHORT_4_4_4_4);
  JS_GL_CONSTANT(UNSIGNED_SHORT_5_5_5_1);
  JS_GL_CONSTANT(UNSIGNED_SHORT_5_6_5);

  /* Shaders */
  JS_GL_CONSTANT(FRAGMENT_SHADER);
  JS_GL_CONSTANT(VERTEX_SHADER);
  JS_GL_CONSTANT(MAX_VERTEX_ATTRIBS);
// #ifndef __APPLE__
  JS_GL_CONSTANT(MAX_VERTEX_UNIFORM_VECTORS);
  JS_GL_CONSTANT(MAX_VARYING_VECTORS);
// #endif
  JS_GL_CONSTANT(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
  JS_GL_CONSTANT(MAX_VERTEX_TEXTURE_IMAGE_UNITS);
  JS_GL_CONSTANT(MAX_TEXTURE_IMAGE_UNITS);
// #ifndef __APPLE__
  JS_GL_CONSTANT(MAX_FRAGMENT_UNIFORM_VECTORS);
// #endif
  JS_GL_CONSTANT(SHADER_TYPE);
  JS_GL_CONSTANT(DELETE_STATUS);
  JS_GL_CONSTANT(LINK_STATUS);
  JS_GL_CONSTANT(VALIDATE_STATUS);
  JS_GL_CONSTANT(ATTACHED_SHADERS);
  JS_GL_CONSTANT(ACTIVE_UNIFORMS);
  JS_GL_CONSTANT(ACTIVE_UNIFORM_MAX_LENGTH);
  JS_GL_CONSTANT(ACTIVE_ATTRIBUTES);
  JS_GL_CONSTANT(ACTIVE_ATTRIBUTE_MAX_LENGTH);
  JS_GL_CONSTANT(SHADING_LANGUAGE_VERSION);
  JS_GL_CONSTANT(CURRENT_PROGRAM);

  /* StencilFunction */
  JS_GL_CONSTANT(NEVER);
  JS_GL_CONSTANT(LESS);
  JS_GL_CONSTANT(EQUAL);
  JS_GL_CONSTANT(LEQUAL);
  JS_GL_CONSTANT(GREATER);
  JS_GL_CONSTANT(NOTEQUAL);
  JS_GL_CONSTANT(GEQUAL);
  JS_GL_CONSTANT(ALWAYS);

  /* StencilOp */
  /*      GL_ZERO */
  JS_GL_CONSTANT(KEEP);
  JS_GL_CONSTANT(REPLACE);
  JS_GL_CONSTANT(INCR);
  JS_GL_CONSTANT(DECR);
  JS_GL_CONSTANT(INVERT);
  JS_GL_CONSTANT(INCR_WRAP);
  JS_GL_CONSTANT(DECR_WRAP);

  /* StringName */
  JS_GL_CONSTANT(VENDOR);
  JS_GL_CONSTANT(RENDERER);
  JS_GL_CONSTANT(VERSION);
  JS_GL_CONSTANT(EXTENSIONS);

  /* TextureMagFilter */
  JS_GL_CONSTANT(NEAREST);
  JS_GL_CONSTANT(LINEAR);

  /* TextureMinFilter */
  /*      GL_NEAREST */
  /*      GL_LINEAR */
  JS_GL_CONSTANT(NEAREST_MIPMAP_NEAREST);
  JS_GL_CONSTANT(LINEAR_MIPMAP_NEAREST);
  JS_GL_CONSTANT(NEAREST_MIPMAP_LINEAR);
  JS_GL_CONSTANT(LINEAR_MIPMAP_LINEAR);

  /* TextureParameterName */
  JS_GL_CONSTANT(TEXTURE_MAG_FILTER);
  JS_GL_CONSTANT(TEXTURE_MIN_FILTER);
  JS_GL_CONSTANT(TEXTURE_WRAP_S);
  JS_GL_CONSTANT(TEXTURE_WRAP_T);

  /* TextureTarget */
  /*      GL_TEXTURE_2D */
  JS_GL_CONSTANT(TEXTURE);

  JS_GL_CONSTANT(TEXTURE_CUBE_MAP);
  JS_GL_CONSTANT(TEXTURE_BINDING_CUBE_MAP);
  JS_GL_CONSTANT(TEXTURE_CUBE_MAP_POSITIVE_X);
  JS_GL_CONSTANT(TEXTURE_CUBE_MAP_NEGATIVE_X);
  JS_GL_CONSTANT(TEXTURE_CUBE_MAP_POSITIVE_Y);
  JS_GL_CONSTANT(TEXTURE_CUBE_MAP_NEGATIVE_Y);
  JS_GL_CONSTANT(TEXTURE_CUBE_MAP_POSITIVE_Z);
  JS_GL_CONSTANT(TEXTURE_CUBE_MAP_NEGATIVE_Z);
  JS_GL_CONSTANT(MAX_CUBE_MAP_TEXTURE_SIZE);

  /* TextureUnit */
  JS_GL_CONSTANT(TEXTURE0);
  JS_GL_CONSTANT(TEXTURE1);
  JS_GL_CONSTANT(TEXTURE2);
  JS_GL_CONSTANT(TEXTURE3);
  JS_GL_CONSTANT(TEXTURE4);
  JS_GL_CONSTANT(TEXTURE5);
  JS_GL_CONSTANT(TEXTURE6);
  JS_GL_CONSTANT(TEXTURE7);
  JS_GL_CONSTANT(TEXTURE8);
  JS_GL_CONSTANT(TEXTURE9);
  JS_GL_CONSTANT(TEXTURE10);
  JS_GL_CONSTANT(TEXTURE11);
  JS_GL_CONSTANT(TEXTURE12);
  JS_GL_CONSTANT(TEXTURE13);
  JS_GL_CONSTANT(TEXTURE14);
  JS_GL_CONSTANT(TEXTURE15);
  JS_GL_CONSTANT(TEXTURE16);
  JS_GL_CONSTANT(TEXTURE17);
  JS_GL_CONSTANT(TEXTURE18);
  JS_GL_CONSTANT(TEXTURE19);
  JS_GL_CONSTANT(TEXTURE20);
  JS_GL_CONSTANT(TEXTURE21);
  JS_GL_CONSTANT(TEXTURE22);
  JS_GL_CONSTANT(TEXTURE23);
  JS_GL_CONSTANT(TEXTURE24);
  JS_GL_CONSTANT(TEXTURE25);
  JS_GL_CONSTANT(TEXTURE26);
  JS_GL_CONSTANT(TEXTURE27);
  JS_GL_CONSTANT(TEXTURE28);
  JS_GL_CONSTANT(TEXTURE29);
  JS_GL_CONSTANT(TEXTURE30);
  JS_GL_CONSTANT(TEXTURE31);
  JS_GL_CONSTANT(ACTIVE_TEXTURE);

  /* TextureWrapMode */
  JS_GL_CONSTANT(REPEAT);
  JS_GL_CONSTANT(CLAMP_TO_EDGE);
  JS_GL_CONSTANT(MIRRORED_REPEAT);

  /* Uniform Types */
  JS_GL_CONSTANT(FLOAT_VEC2);
  JS_GL_CONSTANT(FLOAT_VEC3);
  JS_GL_CONSTANT(FLOAT_VEC4);
  JS_GL_CONSTANT(INT_VEC2);
  JS_GL_CONSTANT(INT_VEC3);
  JS_GL_CONSTANT(INT_VEC4);
  JS_GL_CONSTANT(BOOL);
  JS_GL_CONSTANT(BOOL_VEC2);
  JS_GL_CONSTANT(BOOL_VEC3);
  JS_GL_CONSTANT(BOOL_VEC4);
  JS_GL_CONSTANT(FLOAT_MAT2);
  JS_GL_CONSTANT(FLOAT_MAT3);
  JS_GL_CONSTANT(FLOAT_MAT4);
  JS_GL_CONSTANT(SAMPLER_2D);
  JS_GL_CONSTANT(SAMPLER_CUBE);

  /* Vertex Arrays */
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_ENABLED);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_SIZE);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_STRIDE);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_TYPE);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_NORMALIZED);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_POINTER);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_BUFFER_BINDING);

  /* Read Format */
// #ifndef __APPLE__
  JS_GL_CONSTANT(IMPLEMENTATION_COLOR_READ_TYPE);
  JS_GL_CONSTANT(IMPLEMENTATION_COLOR_READ_FORMAT);
// #endif

  /* Shader Source */
  JS_GL_CONSTANT(COMPILE_STATUS);
  JS_GL_CONSTANT(INFO_LOG_LENGTH);
  JS_GL_CONSTANT(SHADER_SOURCE_LENGTH);
// #ifndef __APPLE__
  JS_GL_CONSTANT(SHADER_COMPILER);
// #endif

  /* Shader Binary */
// #ifndef __APPLE__
  JS_GL_CONSTANT(SHADER_BINARY_FORMATS);
  JS_GL_CONSTANT(NUM_SHADER_BINARY_FORMATS);
// #endif

  /* Shader Precision-Specified Types */
// #ifndef __APPLE__
  JS_GL_CONSTANT(LOW_FLOAT);
  JS_GL_CONSTANT(MEDIUM_FLOAT);
  JS_GL_CONSTANT(HIGH_FLOAT);
  JS_GL_CONSTANT(LOW_INT);
  JS_GL_CONSTANT(MEDIUM_INT);
  JS_GL_CONSTANT(HIGH_INT);
// #endif

  /* Framebuffer Object. */
  JS_GL_CONSTANT(FRAMEBUFFER);
  JS_GL_CONSTANT(READ_FRAMEBUFFER);
  JS_GL_CONSTANT(DRAW_FRAMEBUFFER);
  JS_GL_CONSTANT(RENDERBUFFER);

  JS_GL_CONSTANT(RGBA4);
  JS_GL_CONSTANT(RGB5_A1);
// #ifndef __APPLE__
  //JS_GL_CONSTANT(RGB565);
// #endif
  JS_GL_CONSTANT(DEPTH_COMPONENT16);
  // JS_GL_CONSTANT(STENCIL_INDEX);
  JS_GL_CONSTANT(STENCIL_INDEX8);
  JS_GL_CONSTANT(DEPTH_STENCIL);
  JS_GL_CONSTANT(DEPTH24_STENCIL8);

  JS_GL_CONSTANT(RENDERBUFFER_WIDTH);
  JS_GL_CONSTANT(RENDERBUFFER_HEIGHT);
  JS_GL_CONSTANT(RENDERBUFFER_INTERNAL_FORMAT);
  JS_GL_CONSTANT(RENDERBUFFER_RED_SIZE);
  JS_GL_CONSTANT(RENDERBUFFER_GREEN_SIZE);
  JS_GL_CONSTANT(RENDERBUFFER_BLUE_SIZE);
  JS_GL_CONSTANT(RENDERBUFFER_ALPHA_SIZE);
  JS_GL_CONSTANT(RENDERBUFFER_DEPTH_SIZE);
  JS_GL_CONSTANT(RENDERBUFFER_STENCIL_SIZE);

  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE);

  JS_GL_CONSTANT(COLOR_ATTACHMENT0);
  JS_GL_CONSTANT(COLOR_ATTACHMENT1);
  JS_GL_CONSTANT(COLOR_ATTACHMENT2);
  JS_GL_CONSTANT(COLOR_ATTACHMENT3);
  JS_GL_CONSTANT(COLOR_ATTACHMENT4);
  JS_GL_CONSTANT(COLOR_ATTACHMENT5);
  JS_GL_CONSTANT(COLOR_ATTACHMENT6);
  JS_GL_CONSTANT(COLOR_ATTACHMENT7);
  JS_GL_CONSTANT(COLOR_ATTACHMENT8);
  JS_GL_CONSTANT(COLOR_ATTACHMENT9);
  JS_GL_CONSTANT(COLOR_ATTACHMENT10);
  JS_GL_CONSTANT(COLOR_ATTACHMENT11);
  JS_GL_CONSTANT(COLOR_ATTACHMENT12);
  JS_GL_CONSTANT(COLOR_ATTACHMENT13);
  JS_GL_CONSTANT(COLOR_ATTACHMENT14);
  JS_GL_CONSTANT(COLOR_ATTACHMENT15);
  JS_GL_CONSTANT(DEPTH_ATTACHMENT);
  JS_GL_CONSTANT(STENCIL_ATTACHMENT);
  JS_GL_CONSTANT(DEPTH_STENCIL_ATTACHMENT);
  JS_GL_CONSTANT(DRAW_BUFFER0);
  JS_GL_CONSTANT(DRAW_BUFFER1);
  JS_GL_CONSTANT(DRAW_BUFFER2);
  JS_GL_CONSTANT(DRAW_BUFFER3);
  JS_GL_CONSTANT(DRAW_BUFFER4);
  JS_GL_CONSTANT(DRAW_BUFFER5);
  JS_GL_CONSTANT(DRAW_BUFFER6);
  JS_GL_CONSTANT(DRAW_BUFFER7);
  JS_GL_CONSTANT(DRAW_BUFFER8);
  JS_GL_CONSTANT(DRAW_BUFFER9);
  JS_GL_CONSTANT(DRAW_BUFFER10);
  JS_GL_CONSTANT(DRAW_BUFFER11);
  JS_GL_CONSTANT(DRAW_BUFFER12);
  JS_GL_CONSTANT(DRAW_BUFFER13);
  JS_GL_CONSTANT(DRAW_BUFFER14);
  JS_GL_CONSTANT(DRAW_BUFFER15);
  JS_GL_CONSTANT(MAX_COLOR_ATTACHMENTS);
  JS_GL_CONSTANT(MAX_DRAW_BUFFERS);

  JS_GL_CONSTANT(NONE);

  JS_GL_CONSTANT(FRAMEBUFFER_COMPLETE);
  JS_GL_CONSTANT(FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
  JS_GL_CONSTANT(FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
// #ifndef __APPLE__
  //JS_GL_CONSTANT(FRAMEBUFFER_INCOMPLETE_DIMENSIONS);
// #endif
  JS_GL_CONSTANT(FRAMEBUFFER_UNSUPPORTED);

  JS_GL_CONSTANT(FRAMEBUFFER_BINDING);
  JS_GL_CONSTANT(DRAW_FRAMEBUFFER_BINDING);
  JS_GL_CONSTANT(READ_FRAMEBUFFER_BINDING);
  JS_GL_CONSTANT(VERTEX_ARRAY_BINDING);
  JS_GL_CONSTANT(RENDERBUFFER_BINDING);
  JS_GL_CONSTANT(MAX_RENDERBUFFER_SIZE);

  JS_GL_CONSTANT(INVALID_FRAMEBUFFER_OPERATION);

  /* Sync objects */
  JS_GL_CONSTANT(OBJECT_TYPE);
  JS_GL_CONSTANT(SYNC_FENCE);
  JS_GL_CONSTANT(SYNC_STATUS);
  JS_GL_CONSTANT(SIGNALED);
  JS_GL_CONSTANT(UNSIGNALED);
  JS_GL_CONSTANT(SYNC_CONDITION);
  JS_GL_CONSTANT(SYNC_GPU_COMMANDS_COMPLETE);
  JS_GL_CONSTANT(SYNC_FLAGS);
  JS_GL_CONSTANT(SYNC_FLUSH_COMMANDS_BIT);
  JS_GL_CONSTANT(ALREADY_SIGNALED);
  JS_GL_CONSTANT(TIMEOUT_EXPIRED);
  JS_GL_CONSTANT(CONDITION_SATISFIED);
  JS_GL_CONSTANT(WAIT_FAILED);
  GLint64 GL_TIMEOUT_IGNORED_TEMP_64 = GL_TIMEOUT_IGNORED;
  double GL_TIMEOUT_IGNORED_TEMP_DOUBLE = *(double *)(&GL_TIMEOUT_IGNORED_TEMP_64);
  proto->Set(JS_STR("TIMEOUT_IGNORED"), Nan::New<v8::Number>(GL_TIMEOUT_IGNORED_TEMP_DOUBLE));

  /* Floating Point Textures */
  JS_GL_CONSTANT(HALF_FLOAT);
  JS_GL_CONSTANT(FLOAT);
  JS_GL_CONSTANT(R8);
  JS_GL_CONSTANT(R16F);
  JS_GL_CONSTANT(R32F);
  JS_GL_CONSTANT(R8UI);
  JS_GL_CONSTANT(RG8);
  JS_GL_CONSTANT(RG16F);
  JS_GL_CONSTANT(RG32F);
  JS_GL_CONSTANT(RG8UI);
  JS_GL_CONSTANT(RG16UI);
  JS_GL_CONSTANT(RG32UI);
  JS_GL_CONSTANT(RGB8);
  JS_GL_CONSTANT(SRGB8);
  JS_GL_CONSTANT(RGB565);
  JS_GL_CONSTANT(R11F_G11F_B10F);
  JS_GL_CONSTANT(RGB9_E5);
  JS_GL_CONSTANT(RGB16F);
  JS_GL_CONSTANT(RGB32F);
  JS_GL_CONSTANT(RGB8UI);
  JS_GL_CONSTANT(RGBA8);
  JS_GL_CONSTANT(RGB5_A1);
  JS_GL_CONSTANT(RGB10_A2);
  JS_GL_CONSTANT(RGBA4);
  JS_GL_CONSTANT(RGBA16F);
  JS_GL_CONSTANT(RGBA32F);
  JS_GL_CONSTANT(RGBA8UI);

  /* WebGL-specific enums */
  JS_GL_SET_CONSTANT("UNPACK_FLIP_Y_WEBGL", UNPACK_FLIP_Y_WEBGL);
  JS_GL_SET_CONSTANT("UNPACK_PREMULTIPLY_ALPHA_WEBGL", UNPACK_PREMULTIPLY_ALPHA_WEBGL);
  JS_GL_SET_CONSTANT("CONTEXT_LOST_WEBGL", CONTEXT_LOST_WEBGL);
  JS_GL_SET_CONSTANT("UNPACK_COLORSPACE_CONVERSION_WEBGL", UNPACK_COLORSPACE_CONVERSION_WEBGL);
  JS_GL_SET_CONSTANT("BROWSER_DEFAULT_WEBGL", BROWSER_DEFAULT_WEBGL);
  JS_GL_SET_CONSTANT("MAX_CLIENT_WAIT_TIMEOUT_WEBGL", MAX_CLIENT_WAIT_TIMEOUT_WEBGL);

  //////////////////////////////
  // NOT in WebGL spec
  //////////////////////////////

  // PBO
  JS_GL_SET_CONSTANT("PIXEL_PACK_BUFFER_BINDING" , 0x88ED);
  JS_GL_SET_CONSTANT("PIXEL_UNPACK_BUFFER_BINDING", 0x88EF);

  // external
  JS_GL_SET_CONSTANT("TEXTURE_EXTERNAL_OES", 0x8D65);

  //----------------------------------------------------------------------------
  // WebGL 2 constants from:
  // https://www.khronos.org/registry/webgl/specs/latest/2.0/
  // Those commented out already defined for WebGL
  JS_GL_CONSTANT(READ_BUFFER);
  JS_GL_CONSTANT(UNPACK_ROW_LENGTH);
  JS_GL_CONSTANT(UNPACK_SKIP_ROWS);
  JS_GL_CONSTANT(UNPACK_SKIP_PIXELS);
  JS_GL_CONSTANT(PACK_ROW_LENGTH);
  JS_GL_CONSTANT(PACK_SKIP_ROWS);
  JS_GL_CONSTANT(PACK_SKIP_PIXELS);
  JS_GL_CONSTANT(COLOR);
  JS_GL_CONSTANT(DEPTH);
  JS_GL_CONSTANT(STENCIL);
  JS_GL_CONSTANT(RED);
  //JS_GL_CONSTANT(RGB8);
  //JS_GL_CONSTANT(RGBA8);
  //JS_GL_CONSTANT(RGB10_A2);
  JS_GL_CONSTANT(TEXTURE_BINDING_3D);
  JS_GL_CONSTANT(UNPACK_SKIP_IMAGES);
  JS_GL_CONSTANT(UNPACK_IMAGE_HEIGHT);
  JS_GL_CONSTANT(TEXTURE_3D);
  JS_GL_CONSTANT(TEXTURE_WRAP_R);
  JS_GL_CONSTANT(MAX_3D_TEXTURE_SIZE);
  JS_GL_CONSTANT(UNSIGNED_INT_2_10_10_10_REV);
  JS_GL_CONSTANT(MAX_ELEMENTS_VERTICES);
  JS_GL_CONSTANT(MAX_ELEMENTS_INDICES);
  JS_GL_CONSTANT(TEXTURE_MIN_LOD);
  JS_GL_CONSTANT(TEXTURE_MAX_LOD);
  JS_GL_CONSTANT(TEXTURE_BASE_LEVEL);
  JS_GL_CONSTANT(TEXTURE_MAX_LEVEL);
  JS_GL_CONSTANT(MIN);
  JS_GL_CONSTANT(MAX);
  JS_GL_CONSTANT(DEPTH_COMPONENT24);
  JS_GL_CONSTANT(MAX_TEXTURE_LOD_BIAS);
  JS_GL_CONSTANT(TEXTURE_COMPARE_MODE);
  JS_GL_CONSTANT(TEXTURE_COMPARE_FUNC);
  JS_GL_CONSTANT(CURRENT_QUERY);
  JS_GL_CONSTANT(QUERY_RESULT);
  JS_GL_CONSTANT(QUERY_RESULT_AVAILABLE);
  JS_GL_CONSTANT(STREAM_READ);
  JS_GL_CONSTANT(STREAM_COPY);
  JS_GL_CONSTANT(STATIC_READ);
  JS_GL_CONSTANT(STATIC_COPY);
  JS_GL_CONSTANT(DYNAMIC_READ);
  JS_GL_CONSTANT(DYNAMIC_COPY);
  //JS_GL_CONSTANT(MAX_DRAW_BUFFERS);
  //JS_GL_CONSTANT(DRAW_BUFFER0);
  //JS_GL_CONSTANT(DRAW_BUFFER1);
  //JS_GL_CONSTANT(DRAW_BUFFER2);
  //JS_GL_CONSTANT(DRAW_BUFFER3);
  //JS_GL_CONSTANT(DRAW_BUFFER4);
  //JS_GL_CONSTANT(DRAW_BUFFER5);
  //JS_GL_CONSTANT(DRAW_BUFFER6);
  //JS_GL_CONSTANT(DRAW_BUFFER7);
  //JS_GL_CONSTANT(DRAW_BUFFER8);
  //JS_GL_CONSTANT(DRAW_BUFFER9);
  //JS_GL_CONSTANT(DRAW_BUFFER10);
  //JS_GL_CONSTANT(DRAW_BUFFER11);
  //JS_GL_CONSTANT(DRAW_BUFFER12);
  //JS_GL_CONSTANT(DRAW_BUFFER13);
  //JS_GL_CONSTANT(DRAW_BUFFER14);
  //JS_GL_CONSTANT(DRAW_BUFFER15);
  JS_GL_CONSTANT(MAX_FRAGMENT_UNIFORM_COMPONENTS);
  JS_GL_CONSTANT(MAX_VERTEX_UNIFORM_COMPONENTS);
  JS_GL_CONSTANT(SAMPLER_3D);
  JS_GL_CONSTANT(SAMPLER_2D_SHADOW);
  JS_GL_CONSTANT(FRAGMENT_SHADER_DERIVATIVE_HINT);
  //JS_GL_CONSTANT(PIXEL_PACK_BUFFER);
  //JS_GL_CONSTANT(PIXEL_UNPACK_BUFFER);
  //JS_GL_CONSTANT(PIXEL_PACK_BUFFER_BINDING);
  //JS_GL_CONSTANT(PIXEL_UNPACK_BUFFER_BINDING);
  JS_GL_CONSTANT(FLOAT_MAT2x3);
  JS_GL_CONSTANT(FLOAT_MAT2x4);
  JS_GL_CONSTANT(FLOAT_MAT3x2);
  JS_GL_CONSTANT(FLOAT_MAT3x4);
  JS_GL_CONSTANT(FLOAT_MAT4x2);
  JS_GL_CONSTANT(FLOAT_MAT4x3);
  JS_GL_CONSTANT(SRGB);
  //JS_GL_CONSTANT(SRGB8);
  JS_GL_CONSTANT(SRGB8_ALPHA8);
  JS_GL_CONSTANT(COMPARE_REF_TO_TEXTURE);
  //JS_GL_CONSTANT(RGBA32F);
  //JS_GL_CONSTANT(RGB32F);
  //JS_GL_CONSTANT(RGBA16F);
  //JS_GL_CONSTANT(RGB16F);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_INTEGER);
  JS_GL_CONSTANT(MAX_ARRAY_TEXTURE_LAYERS);
  JS_GL_CONSTANT(MIN_PROGRAM_TEXEL_OFFSET);
  JS_GL_CONSTANT(MAX_PROGRAM_TEXEL_OFFSET);
  JS_GL_CONSTANT(MAX_VARYING_COMPONENTS);
  JS_GL_CONSTANT(TEXTURE_2D_ARRAY);
  JS_GL_CONSTANT(TEXTURE_BINDING_2D_ARRAY);
  //JS_GL_CONSTANT(R11F_G11F_B10F);
  JS_GL_CONSTANT(UNSIGNED_INT_10F_11F_11F_REV);
  //JS_GL_CONSTANT(RGB9_E5);
  JS_GL_CONSTANT(UNSIGNED_INT_5_9_9_9_REV);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BUFFER_MODE);
  JS_GL_CONSTANT(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_VARYINGS);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BUFFER_START);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BUFFER_SIZE);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
  JS_GL_CONSTANT(RASTERIZER_DISCARD);
  JS_GL_CONSTANT(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS);
  JS_GL_CONSTANT(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS);
  JS_GL_CONSTANT(INTERLEAVED_ATTRIBS);
  JS_GL_CONSTANT(SEPARATE_ATTRIBS);
  //JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BUFFER);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BUFFER_BINDING);
  JS_GL_CONSTANT(RGBA32UI);
  JS_GL_CONSTANT(RGB32UI);
  JS_GL_CONSTANT(RGBA16UI);
  JS_GL_CONSTANT(RGB16UI);
  //JS_GL_CONSTANT(RGBA8UI);
  //JS_GL_CONSTANT(RGB8UI);
  JS_GL_CONSTANT(RGBA32I);
  JS_GL_CONSTANT(RGB32I);
  JS_GL_CONSTANT(RGBA16I);
  JS_GL_CONSTANT(RGB16I);
  JS_GL_CONSTANT(RGBA8I);
  JS_GL_CONSTANT(RGB8I);
  JS_GL_CONSTANT(RED_INTEGER);
  JS_GL_CONSTANT(RGB_INTEGER);
  JS_GL_CONSTANT(RGBA_INTEGER);
  JS_GL_CONSTANT(SAMPLER_2D_ARRAY);
  JS_GL_CONSTANT(SAMPLER_2D_ARRAY_SHADOW);
  JS_GL_CONSTANT(SAMPLER_CUBE_SHADOW);
  JS_GL_CONSTANT(UNSIGNED_INT_VEC2);
  JS_GL_CONSTANT(UNSIGNED_INT_VEC3);
  JS_GL_CONSTANT(UNSIGNED_INT_VEC4);
  JS_GL_CONSTANT(INT_SAMPLER_2D);
  JS_GL_CONSTANT(INT_SAMPLER_3D);
  JS_GL_CONSTANT(INT_SAMPLER_CUBE);
  JS_GL_CONSTANT(INT_SAMPLER_2D_ARRAY);
  JS_GL_CONSTANT(UNSIGNED_INT_SAMPLER_2D);
  JS_GL_CONSTANT(UNSIGNED_INT_SAMPLER_3D);
  JS_GL_CONSTANT(UNSIGNED_INT_SAMPLER_CUBE);
  JS_GL_CONSTANT(UNSIGNED_INT_SAMPLER_2D_ARRAY);
  JS_GL_CONSTANT(DEPTH_COMPONENT32F);
  JS_GL_CONSTANT(DEPTH32F_STENCIL8);
  JS_GL_CONSTANT(FLOAT_32_UNSIGNED_INT_24_8_REV);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_RED_SIZE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_GREEN_SIZE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_BLUE_SIZE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
  JS_GL_CONSTANT(FRAMEBUFFER_DEFAULT);
  //JS_GL_CONSTANT(DEPTH_STENCIL_ATTACHMENT);
  //JS_GL_CONSTANT(DEPTH_STENCIL);
  JS_GL_CONSTANT(UNSIGNED_INT_24_8);
  //JS_GL_CONSTANT(DEPTH24_STENCIL8);
  JS_GL_CONSTANT(UNSIGNED_NORMALIZED);
  //JS_GL_CONSTANT(DRAW_FRAMEBUFFER_BINDING); /* Same as FRAMEBUFFER_BINDING */
  //JS_GL_CONSTANT(READ_FRAMEBUFFER);
  //JS_GL_CONSTANT(DRAW_FRAMEBUFFER);
  //JS_GL_CONSTANT(READ_FRAMEBUFFER_BINDING);
  JS_GL_CONSTANT(RENDERBUFFER_SAMPLES);
  JS_GL_CONSTANT(FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER);
  //JS_GL_CONSTANT(MAX_COLOR_ATTACHMENTS);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT1);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT2);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT3);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT4);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT5);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT6);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT7);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT8);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT9);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT10);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT11);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT12);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT13);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT14);
  //JS_GL_CONSTANT(COLOR_ATTACHMENT15);
  JS_GL_CONSTANT(FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
  JS_GL_CONSTANT(MAX_SAMPLES);
  //JS_GL_CONSTANT(HALF_FLOAT);
  JS_GL_CONSTANT(RG);
  JS_GL_CONSTANT(RG_INTEGER);
  //JS_GL_CONSTANT(R8);
  //JS_GL_CONSTANT(RG8);
  //JS_GL_CONSTANT(R16F);
  //JS_GL_CONSTANT(R32F);
  //JS_GL_CONSTANT(RG16F);
  //JS_GL_CONSTANT(RG32F);
  JS_GL_CONSTANT(R8I);
  //JS_GL_CONSTANT(R8UI);
  JS_GL_CONSTANT(R16I);
  JS_GL_CONSTANT(R16UI);
  JS_GL_CONSTANT(R32I);
  JS_GL_CONSTANT(R32UI);
  JS_GL_CONSTANT(RG8I);
  //JS_GL_CONSTANT(RG8UI);
  JS_GL_CONSTANT(RG16I);
  //JS_GL_CONSTANT(RG16UI);
  JS_GL_CONSTANT(RG32I);
  //JS_GL_CONSTANT(RG32UI);
  //JS_GL_CONSTANT(VERTEX_ARRAY_BINDING);
  JS_GL_CONSTANT(R8_SNORM);
  JS_GL_CONSTANT(RG8_SNORM);
  JS_GL_CONSTANT(RGB8_SNORM);
  JS_GL_CONSTANT(RGBA8_SNORM);
  JS_GL_CONSTANT(SIGNED_NORMALIZED);
  //JS_GL_CONSTANT(COPY_READ_BUFFER);
  //JS_GL_CONSTANT(COPY_WRITE_BUFFER);
  JS_GL_CONSTANT(COPY_READ_BUFFER_BINDING); /* Same as COPY_READ_BUFFER */
  JS_GL_CONSTANT(COPY_WRITE_BUFFER_BINDING); /* Same as COPY_WRITE_BUFFER */
  //JS_GL_CONSTANT(UNIFORM_BUFFER);
  JS_GL_CONSTANT(UNIFORM_BUFFER_BINDING);
  JS_GL_CONSTANT(UNIFORM_BUFFER_START);
  JS_GL_CONSTANT(UNIFORM_BUFFER_SIZE);
  JS_GL_CONSTANT(MAX_VERTEX_UNIFORM_BLOCKS);
  JS_GL_CONSTANT(MAX_FRAGMENT_UNIFORM_BLOCKS);
  JS_GL_CONSTANT(MAX_COMBINED_UNIFORM_BLOCKS);
  JS_GL_CONSTANT(MAX_UNIFORM_BUFFER_BINDINGS);
  JS_GL_CONSTANT(MAX_UNIFORM_BLOCK_SIZE);
  JS_GL_CONSTANT(MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS);
  JS_GL_CONSTANT(MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS);
  JS_GL_CONSTANT(UNIFORM_BUFFER_OFFSET_ALIGNMENT);
  JS_GL_CONSTANT(ACTIVE_UNIFORM_BLOCKS);
  JS_GL_CONSTANT(UNIFORM_TYPE);
  JS_GL_CONSTANT(UNIFORM_SIZE);
  JS_GL_CONSTANT(UNIFORM_BLOCK_INDEX);
  JS_GL_CONSTANT(UNIFORM_OFFSET);
  JS_GL_CONSTANT(UNIFORM_ARRAY_STRIDE);
  JS_GL_CONSTANT(UNIFORM_MATRIX_STRIDE);
  JS_GL_CONSTANT(UNIFORM_IS_ROW_MAJOR);
  JS_GL_CONSTANT(UNIFORM_BLOCK_BINDING);
  JS_GL_CONSTANT(UNIFORM_BLOCK_DATA_SIZE);
  JS_GL_CONSTANT(UNIFORM_BLOCK_ACTIVE_UNIFORMS);
  JS_GL_CONSTANT(UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES);
  JS_GL_CONSTANT(UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER);
  JS_GL_CONSTANT(UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER);
  JS_GL_CONSTANT(INVALID_INDEX);
  JS_GL_CONSTANT(MAX_VERTEX_OUTPUT_COMPONENTS);
  JS_GL_CONSTANT(MAX_FRAGMENT_INPUT_COMPONENTS);
  JS_GL_CONSTANT(MAX_SERVER_WAIT_TIMEOUT);
  //JS_GL_CONSTANT(OBJECT_TYPE);
  //JS_GL_CONSTANT(SYNC_CONDITION);
  //JS_GL_CONSTANT(SYNC_STATUS);
  //JS_GL_CONSTANT(SYNC_FLAGS);
  //JS_GL_CONSTANT(SYNC_FENCE);
  //JS_GL_CONSTANT(SYNC_GPU_COMMANDS_COMPLETE);
  //JS_GL_CONSTANT(UNSIGNALED);
  //JS_GL_CONSTANT(SIGNALED);
  //JS_GL_CONSTANT(ALREADY_SIGNALED);
  //JS_GL_CONSTANT(TIMEOUT_EXPIRED);
  //JS_GL_CONSTANT(CONDITION_SATISFIED);
  //JS_GL_CONSTANT(WAIT_FAILED);
  //JS_GL_CONSTANT(SYNC_FLUSH_COMMANDS_BIT);
  JS_GL_CONSTANT(VERTEX_ATTRIB_ARRAY_DIVISOR);
  JS_GL_CONSTANT(ANY_SAMPLES_PASSED);
  JS_GL_CONSTANT(ANY_SAMPLES_PASSED_CONSERVATIVE);
  JS_GL_CONSTANT(SAMPLER_BINDING);
  JS_GL_CONSTANT(RGB10_A2UI);
  JS_GL_CONSTANT(INT_2_10_10_10_REV);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_PAUSED);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_ACTIVE);
  JS_GL_CONSTANT(TRANSFORM_FEEDBACK_BINDING);
  JS_GL_CONSTANT(TEXTURE_IMMUTABLE_FORMAT);
  JS_GL_CONSTANT(MAX_ELEMENT_INDEX);
  JS_GL_CONSTANT(TEXTURE_IMMUTABLE_LEVELS);

  //const GLint64 TIMEOUT_IGNORED                              = -1;

  /* WebGL-specific enums */
  //JS_GL_SET_CONSTANT("MAX_CLIENT_WAIT_TIMEOUT_WEBGL", MAX_CLIENT_WAIT_TIMEOUT_WEBGL);
}

// state tracking

ViewportState::ViewportState(GLint x, GLint y, GLsizei w, GLsizei h, bool valid) : x(x), y(y), w(w), h(h), valid(valid) {}

ViewportState &ViewportState::operator=(const ViewportState &viewportState) {
  x = viewportState.x;
  y = viewportState.y;
  w = viewportState.w;
  h = viewportState.h;
  valid = viewportState.valid;

  return *this;
}

ColorMaskState::ColorMaskState(GLboolean r, GLboolean g, GLboolean b, GLboolean a, bool valid) : r(r), g(g), b(b), a(a), valid(valid) {}

ColorMaskState &ColorMaskState::operator=(const ColorMaskState &colorMaskState) {
  r = colorMaskState.r;
  g = colorMaskState.g;
  b = colorMaskState.b;
  a = colorMaskState.a;
  valid = true;

  return *this;
}

// utils

inline bool hasWidthHeight(Local<Value> &value) {
  MaybeLocal<Object> valueObject(Nan::To<Object>(value));
  if (!valueObject.IsEmpty()) {
    Local<String> widthString = Nan::New<String>("width", sizeof("width") - 1).ToLocalChecked();
    Local<String> heightString = Nan::New<String>("height", sizeof("height") - 1).ToLocalChecked();

    MaybeLocal<Number> widthValue(Nan::To<Number>(valueObject.ToLocalChecked()->Get(widthString)));
    MaybeLocal<Number> heightValue(Nan::To<Number>(valueObject.ToLocalChecked()->Get(heightString)));
    return !widthValue.IsEmpty() && !heightValue.IsEmpty();
  } else {
    return false;
  }
}

size_t getFormatSize(int format) {
  switch (format) {
    case GL_RED:
    case GL_RED_INTEGER:
    case GL_DEPTH_COMPONENT:
    case GL_LUMINANCE:
    case GL_ALPHA:
      return 1;
    case GL_RG:
    case GL_RG_INTEGER:
    case GL_DEPTH_STENCIL:
    case GL_LUMINANCE_ALPHA:
      return 2;
    case GL_RGB:
    case GL_RGB_INTEGER:
      return 3;
    case GL_RGBA:
    case GL_RGBA_INTEGER:
      return 4;
    default:
      return 4;
  }
}

size_t getTypeSize(int type) {
  switch (type) {
    case GL_UNSIGNED_BYTE:
    case GL_BYTE:
      return 1;
    case GL_UNSIGNED_SHORT:
    case GL_SHORT:
    case GL_HALF_FLOAT:
    case GL_UNSIGNED_SHORT_5_6_5:
    case GL_UNSIGNED_SHORT_4_4_4_4:
    case GL_UNSIGNED_SHORT_5_5_5_1:
      return 2;
    case GL_UNSIGNED_INT:
    case GL_INT:
    case GL_FLOAT:
    case GL_UNSIGNED_INT_2_10_10_10_REV:
    case GL_UNSIGNED_INT_10F_11F_11F_REV:
    case GL_UNSIGNED_INT_5_9_9_9_REV:
    case GL_UNSIGNED_INT_24_8:
    case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
      return 4;
    default:
      return 4;
  }
}

int formatMap[] = {
  GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE,
  GL_RGBA8_SNORM, GL_RGBA, GL_BYTE,
  GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4,
  GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV,
  GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1,
  GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT,
  GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT_OES,
  GL_RGBA32F, GL_RGBA, GL_FLOAT,
  GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE,
  GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE,
  GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT,
  GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT,
  GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT,
  GL_RGBA32I, GL_RGBA_INTEGER, GL_INT,
  GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV,
  GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE,
  GL_RGB8_SNORM, GL_RGB, GL_BYTE,
  GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
  GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV,
  GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV,
  GL_RGB16F, GL_RGB, GL_HALF_FLOAT,
  GL_RGB16F, GL_RGB, GL_HALF_FLOAT_OES,
  GL_RGB32F, GL_RGB, GL_FLOAT,
  GL_RGB8UI, GL_RGB_INTEGER, GL_UNSIGNED_BYTE,
  GL_RGB8I, GL_RGB_INTEGER, GL_BYTE,
  GL_RGB16UI, GL_RGB_INTEGER, GL_UNSIGNED_SHORT,
  GL_RGB16I, GL_RGB_INTEGER, GL_SHORT,
  GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT,
  GL_RGB32I, GL_RGB_INTEGER, GL_INT,
  GL_RG8, GL_RG, GL_UNSIGNED_BYTE,
  GL_RG8_SNORM, GL_RG, GL_BYTE,
  GL_RG16F, GL_RG, GL_HALF_FLOAT,
  GL_RG16F, GL_RG, GL_HALF_FLOAT_OES,
  GL_RG32F, GL_RG, GL_FLOAT,
  GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE,
  GL_RG8I, GL_RG_INTEGER, GL_BYTE,
  GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT,
  GL_RG16I, GL_RG_INTEGER, GL_SHORT,
  GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT,
  GL_RG32I, GL_RG_INTEGER, GL_INT,
  GL_R8, GL_RED, GL_UNSIGNED_BYTE,
  GL_R8_SNORM, GL_RED, GL_BYTE,
  GL_R16F, GL_RED, GL_HALF_FLOAT,
  GL_R16F, GL_RED, GL_HALF_FLOAT_OES,
  GL_R32F, GL_RED, GL_FLOAT,
  GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE,
  GL_R8I, GL_RED_INTEGER, GL_BYTE,
  GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT,
  GL_R16I, GL_RED_INTEGER, GL_SHORT,
  GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT,
  GL_R32I, GL_RED_INTEGER, GL_INT,
};

int normalizeInternalFormat(int internalformat, int format, int type) {
  if (internalformat == GL_RED || internalformat == GL_RG || internalformat == GL_RGB || internalformat == GL_RGBA) {
    for (size_t i = 0; i < sizeof(formatMap)/3; i++) {
      int b = formatMap[i * 3 + 1];
      int c = formatMap[i * 3 + 2];
      if (format == b && type == c) {
        int a = formatMap[i * 3 + 0];
        internalformat = a;
        break;
      }
    }
  }
  return internalformat;
}

int getImageFormat(Local<Value> arg) {
  if (arg->IsArrayBufferView()) {
    return -1;
  } else {
    Local<Value> constructorName = JS_OBJ(JS_OBJ(arg)->Get(JS_STR("constructor")))->Get(JS_STR("name"));
    if (
      constructorName->StrictEquals(JS_STR("HTMLImageElement")) ||
      constructorName->StrictEquals(JS_STR("HTMLVideoElement")) ||
      constructorName->StrictEquals(JS_STR("ImageData")) ||
      constructorName->StrictEquals(JS_STR("ImageBitmap")) ||
      constructorName->StrictEquals(JS_STR("HTMLCanvasElement"))
    ) {
      return GL_RGBA;
    } else {
      return -1;
    }
  }
}

size_t getArrayBufferViewElementSize(Local<ArrayBufferView> arrayBufferView) {
  if (arrayBufferView->IsFloat64Array()) {
    return 8;
  } else if (arrayBufferView->IsFloat32Array() || arrayBufferView->IsUint32Array() || arrayBufferView->IsInt32Array()) {
    return 4;
  } else if (arrayBufferView->IsUint16Array() || arrayBufferView->IsInt16Array()) {
    return 2;
  } else {
    return 1;
  }
}

// A 32-bit and 64-bit compatible way of converting a pointer to a GLuint.
static GLuint toGLuint(const void* ptr) {
  return static_cast<GLuint>(reinterpret_cast<size_t>(ptr));
}

template<typename Type>
inline Type* getArrayData(Local<Value> arg, int* num = NULL) {
  Type *data=NULL;
  if (num) {
    *num = 0;
  }

  if (!arg->IsNull()) {
    if (arg->IsArrayBufferView()) {
      Local<ArrayBufferView> arr = Local<ArrayBufferView>::Cast(arg);
      if (num) {
        *num = arr->ByteLength()/sizeof(Type);
      }
      data = reinterpret_cast<Type*>((char *)arr->Buffer()->GetContents().Data() + arr->ByteOffset());
    } else {
      Nan::ThrowError("Bad array argument");
    }
  }

  return data;
}

inline GLuint getImageTexture(Local<Value> arg) {
  GLuint tex = 0;

  if (arg->IsObject()) {
    Local<String> textureString = String::NewFromUtf8(Isolate::GetCurrent(), "texture", NewStringType::kInternalized).ToLocalChecked();
    Local<Value> textureVal = Local<Object>::Cast(arg)->Get(textureString);

    if (textureVal->IsObject()) {
      Local<String> idString = String::NewFromUtf8(Isolate::GetCurrent(), "id", NewStringType::kInternalized).ToLocalChecked();
      Local<Value> idVal = Local<Object>::Cast(textureVal)->Get(idString);

      if (idVal->IsNumber()) {
        tex = TO_UINT32(idVal);
      }
    }
  }
  return tex;
}

inline void *getImageData(Local<Value> arg) {
  void *pixels = nullptr;

  if (!arg->IsNull()) {
    Local<Object> obj = Local<Object>::Cast(arg);
    if (obj->IsObject()) {
      if (obj->IsArrayBufferView()) {
        pixels = getArrayData<unsigned char>(obj);
      } else {
        Local<String> dataString = String::NewFromUtf8(Isolate::GetCurrent(), "data", NewStringType::kInternalized).ToLocalChecked();
        if (Nan::Has(obj, dataString).FromJust()) {
          Local<Value> data = obj->Get(dataString);
          pixels = getArrayData<unsigned char>(data);
        } else {
          Nan::ThrowError("Bad texture argument");
          // pixels = node::Buffer::Data(Nan::Get(obj, JS_STR("data")).ToLocalChecked());
        }
      }
    } else {
      Nan::ThrowError("Bad texture argument");
    }
  }
  return pixels;
}

inline void reformatImageData(char *dstData, char *srcData, size_t dstPixelSize, size_t srcPixelSize, size_t numPixels) {
  if (dstPixelSize < srcPixelSize) {
    // size_t clipSize = srcPixelSize - dstPixelSize;
    for (size_t i = 0; i < numPixels; i++) {
      memcpy(dstData + i * dstPixelSize, srcData + i * srcPixelSize, dstPixelSize);
      /* for (size_t j = 0; j < dstPixelSize; j++) {
        dstData[i * dstPixelSize + j] = srcData[i * srcPixelSize + srcPixelSize - clipSize - 1 - j];
      } */
      // memcpy(dstData + i * dstPixelSize, srcData + i * srcPixelSize + clipSize, dstPixelSize);
    }
  } else if (dstPixelSize > srcPixelSize) {
    size_t clipSize = dstPixelSize - srcPixelSize;
    for (size_t i = 0; i < numPixels; i++) {
      memcpy(dstData + i * dstPixelSize, srcData + i * srcPixelSize, srcPixelSize);
      memset(dstData + i * dstPixelSize + srcPixelSize, 0xFF, clipSize);
    }
  } else {
    // the call was pointless, but fulfill the contract anyway
    memcpy(dstData, srcData, dstPixelSize * numPixels);
  }
}

void flipImageData(char *dstData, char *srcData, size_t width, size_t height, size_t pixelSize) {
  size_t stride = width * pixelSize;
  size_t size = width * height * pixelSize;
  for (size_t i = 0; i < height; i++) {
    memcpy(dstData + (i * stride), srcData + size - stride - (i * stride), stride);
  }
}

template <typename T>
void expandLuminance(char *dstData, char *srcData, size_t width, size_t height) {
  size_t size = width * height;
  for (size_t i = 0; i < size; i++) {
    T value = ((T *)srcData)[i];
    ((T *)dstData)[i * 4 + 0] = value;
    ((T *)dstData)[i * 4 + 1] = value;
    ((T *)dstData)[i * 4 + 2] = value;
    ((T *)dstData)[i * 4 + 3] = value;
  }
}

template <typename T>
void expandLuminanceAlpha(char *dstData, char *srcData, size_t width, size_t height) {
  size_t size = width * height;
  for (size_t i = 0; i < size; i++) {
    T luminance = ((T *)srcData)[i*2 + 0];
    T alpha = ((T *)srcData)[i*2 + 1];
    ((T *)dstData)[i * 4 + 0] = luminance;
    ((T *)dstData)[i * 4 + 1] = luminance;
    ((T *)dstData)[i * 4 + 2] = luminance;
    ((T *)dstData)[i * 4 + 3] = alpha;
  }
}

// templates

template<int d>
inline void glTexImage(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *data);
template<>
inline void glTexImage<2>(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *data) {
  glTexImage2D(target, level, internalformat, width, height, border, format, type, data);
}
template<>
inline void glTexImage<3>(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *data) {
  glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, data);
}
template<int d>
void TexImage(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  Isolate *isolate = Isolate::GetCurrent();

  Local<Value> target;
  Local<Value> level;
  Local<Value> internalformat;
  Local<Value> width;
  Local<Value> height;
  Local<Value> depth;
  Local<Value> border;
  Local<Value> format;
  Local<Value> type;
  Local<Value> pixels;
  Local<Value> srcOffset;

  if (d == 2) {
    target = info[0];
    level = info[1];
    internalformat = info[2];
    width = info[3];
    height = info[4];
    border = info[5];
    format = info[6];
    type = info[7];
    pixels = info[8];
    srcOffset = info[9];
  } else {
    target = info[0];
    level = info[1];
    internalformat = info[2];
    width = info[3];
    height = info[4];
    depth = info[5];
    border = info[6];
    format = info[7];
    type = info[8];
    pixels = info[9];
    srcOffset = info[10];
  }

  Local<String> widthString = String::NewFromUtf8(isolate, "width", NewStringType::kInternalized).ToLocalChecked();
  Local<String> heightString = String::NewFromUtf8(isolate, "height", NewStringType::kInternalized).ToLocalChecked();

  if (info.Length() == 6) {
    // width is now format, height is now type, and border is now pixels
    MaybeLocal<Number> targetNumber(Nan::To<Number>(target));
    MaybeLocal<Number> levelNumber(Nan::To<Number>(level));
    MaybeLocal<Number> internalformatNumber(Nan::To<Number>(internalformat));
    MaybeLocal<Number> widthNumber(Nan::To<Number>(width));
    MaybeLocal<Number> heightNumber(Nan::To<Number>(height));
    if (
      !targetNumber.IsEmpty() &&
      !levelNumber.IsEmpty() && !internalformatNumber.IsEmpty() &&
      !widthNumber.IsEmpty() && !heightNumber.IsEmpty() &&
      (border->IsNull() || hasWidthHeight(border))
    ) {
      pixels=border;
      /* if (pixels) {
        pixels = _getImageData(pixels);
      } */
      type=height;
      format=width;
      width = TO_BOOL(border) ? JS_OBJ(border)->Get(widthString) : Number::New(isolate, 1).As<Value>();
      height = TO_BOOL(border) ? JS_OBJ(border)->Get(heightString) : Number::New(isolate, 1).As<Value>();
      // return _texImage2D(target, level, internalformat, width, height, 0, format, type, pixels);
    } else {
      /* LOGI("Loaded string asset %d %d %d %d %d %d %d %d %d",
        target->TypeOf(isolate)->StrictEquals(numberString),
        level->TypeOf(isolate)->StrictEquals(numberString),
        internalformat->TypeOf(isolate)->StrictEquals(numberString),
        width->TypeOf(isolate)->StrictEquals(numberString),
        height->TypeOf(isolate)->StrictEquals(numberString),
        border->IsNull(), // 0
        !border->IsNull() && border->TypeOf(isolate)->StrictEquals(objectString), // 1
        !border->IsNull() && border->TypeOf(isolate)->StrictEquals(objectString) && JS_OBJ(border)->Get(widthString)->TypeOf(isolate)->StrictEquals(numberString), // 0
        !border->IsNull() && border->TypeOf(isolate)->StrictEquals(objectString) && JS_OBJ(border)->Get(heightString)->TypeOf(isolate)->StrictEquals(numberString) // 0
      ); */

      Nan::ThrowError("Expected texImage2D(number target, number level, number internalformat, number format, number type, Image pixels)");
      return;
    }
  } else if (info.Length() == 9) {
    MaybeLocal<Number> targetNumber(Nan::To<Number>(target));
    MaybeLocal<Number> levelNumber(Nan::To<Number>(level));
    MaybeLocal<Number> internalformatNumber(Nan::To<Number>(internalformat));
    MaybeLocal<Number> widthNumber(Nan::To<Number>(width));
    MaybeLocal<Number> heightNumber(Nan::To<Number>(height));
    MaybeLocal<Number> formatNumber(Nan::To<Number>(format));
    MaybeLocal<Number> typeNumber(Nan::To<Number>(type));
    if (
      !targetNumber.IsEmpty() &&
      !levelNumber.IsEmpty() && !internalformat.IsEmpty() &&
      !widthNumber.IsEmpty() && !heightNumber.IsEmpty() &&
      !formatNumber.IsEmpty() && !typeNumber.IsEmpty() &&
      (pixels->IsNull() || pixels->IsObject() || pixels->IsNumber())
    ) {
      // nothing
    } else {
      Nan::ThrowError("Expected texImage2D(number target, number level, number internalformat, number width, number height, number border, number format, number type, ArrayBufferView pixels)");
      return;
    }
  } else if (info.Length() == 10) {
    MaybeLocal<Number> targetNumber(Nan::To<Number>(target));
    MaybeLocal<Number> levelNumber(Nan::To<Number>(level));
    MaybeLocal<Number> internalformatNumber(Nan::To<Number>(internalformat));
    MaybeLocal<Number> widthNumber(Nan::To<Number>(width));
    MaybeLocal<Number> heightNumber(Nan::To<Number>(height));
    MaybeLocal<Number> formatNumber(Nan::To<Number>(format));
    MaybeLocal<Number> typeNumber(Nan::To<Number>(type));
    MaybeLocal<Number> srcOffsetNumber(Nan::To<Number>(srcOffset));
    if (
      !targetNumber.IsEmpty() &&
      !levelNumber.IsEmpty() && !internalformat.IsEmpty() &&
      !widthNumber.IsEmpty() && !heightNumber.IsEmpty() &&
      !formatNumber.IsEmpty() && !typeNumber.IsEmpty()
    ) {
      if (pixels->IsArrayBufferView() && !srcOffsetNumber.IsEmpty()) {
        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(pixels);
        size_t srcOffsetInt = TO_UINT32(srcOffset);
        size_t elementSize = getArrayBufferViewElementSize(arrayBufferView);
        size_t extraOffset = srcOffsetInt * elementSize;
        pixels = Uint8Array::New(arrayBufferView->Buffer(), arrayBufferView->ByteOffset() + extraOffset, arrayBufferView->ByteLength() - extraOffset);
      } else if (pixels->IsNull() || pixels->IsObject()) {
        // nothing
      } else {
        Nan::ThrowError("Expected texImage2D(number target, number level, number internalformat, number width, number height, number border, number format, number type, ArrayBufferView srcData, number srcOffset)");
        return;
      }
    } else {
      Nan::ThrowError("Expected texImage2D(number target, number level, number internalformat, number width, number height, number border, number format, number type, ArrayBufferView srcData, number srcOffset)");
      return;
    }
  } else {
    Nan::ThrowError("Bad texture argument");
    return;
  }

  GLenum targetV = TO_UINT32(target);
  GLenum levelV = TO_UINT32(level);
  GLenum internalformatV = TO_UINT32(internalformat);
  GLsizei widthV = TO_UINT32(width);
  GLsizei heightV = TO_UINT32(height);
  GLsizei depthV = d == 3 ? TO_UINT32(depth) : 0;
  GLint borderV = TO_INT32(border);
  GLenum formatV = TO_UINT32(format);
  GLenum typeV = TO_UINT32(type);

  internalformatV = normalizeInternalFormat(internalformatV, formatV, typeV);

  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint texV;
  char *pixelsV;
  if (pixels->IsNull()) {
    glTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, formatV, typeV, NULL);
  } else if (pixels->IsNumber()) {
    GLintptr offsetV = TO_UINT32(pixels);
    glTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, formatV, typeV, (void *)offsetV);
  } else if ((texV = getImageTexture(pixels)) != 0) {
    glTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, formatV, typeV, NULL);

    GLuint fbos[2];
    glGenFramebuffers(2, fbos);

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texV, 0);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, targetV, gl->HasTextureBinding(gl->activeTexture, targetV) ? gl->GetTextureBinding(gl->activeTexture, targetV) : 0, 0);

    const bool flipY = d == 2 && gl->HasPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) ? (bool)gl->GetPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) : false;
    if (flipY) {
      glBlitFramebuffer(
        0, 0, widthV, heightV,
        0, 0, widthV, heightV,
        GL_COLOR_BUFFER_BIT,
        GL_NEAREST
      );
    } else {
      glBlitFramebuffer(
        0, heightV, widthV, 0,
        0, 0, widthV, heightV,
        GL_COLOR_BUFFER_BIT,
        GL_NEAREST
      );
    }

    // glCopyTexImage2D(targetV, levelV, internalformatV, 0, 0, widthV, heightV, 0);

    glDeleteFramebuffers(2, fbos);

    if (gl->HasFramebufferBinding(GL_READ_FRAMEBUFFER)) {
      glBindFramebuffer(GL_READ_FRAMEBUFFER, gl->GetFramebufferBinding(GL_READ_FRAMEBUFFER));
    } else {
      glBindFramebuffer(GL_READ_FRAMEBUFFER, gl->defaultFramebuffer);
    }
    if (gl->HasFramebufferBinding(GL_DRAW_FRAMEBUFFER)) {
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gl->GetFramebufferBinding(GL_DRAW_FRAMEBUFFER));
    } else {
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gl->defaultFramebuffer);
    }
  } else if ((pixelsV = (char *)getImageData(pixels)) != nullptr) {
    size_t formatSize = getFormatSize(formatV);
    size_t typeSize = getTypeSize(typeV);
    size_t pixelSize = formatSize * typeSize;
    int srcFormatV = getImageFormat(pixels);
    size_t srcFormatSize = getFormatSize(srcFormatV);
    char *pixelsV2;
    unique_ptr<char[]> pixelsV2Buffer;
    bool needsReformat = srcFormatV != -1 && formatSize != srcFormatSize;
    if (needsReformat) {
      pixelsV2Buffer.reset(new char[widthV * heightV * pixelSize]);
      pixelsV2 = pixelsV2Buffer.get();
      reformatImageData(pixelsV2, pixelsV, formatSize * typeSize, srcFormatSize * typeSize, widthV * heightV);

      glPixelStorei(GL_PACK_ALIGNMENT, 1);
      glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    } else {
      pixelsV2 = pixelsV;
    }

    const bool flipY = d == 2 && gl->HasPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) ? (bool)gl->GetPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) : false;
    if (flipY && !pixels->IsArrayBufferView()) {
      unique_ptr<char[]> pixelsV3Buffer(new char[widthV * heightV * pixelSize]);

      flipImageData(pixelsV3Buffer.get(), pixelsV2, widthV, heightV, pixelSize);

      glTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, formatV, typeV, pixelsV3Buffer.get());
    } else if (formatV == GL_LUMINANCE || formatV == GL_ALPHA) {
      unique_ptr<char[]> pixelsV3Buffer(new char[widthV * heightV * 4]);

      if (typeV == GL_UNSIGNED_BYTE) {
        expandLuminance<unsigned char>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_UNSIGNED_INT) {
        expandLuminance<unsigned int>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_INT) {
        expandLuminance<int>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_UNSIGNED_SHORT) {
        expandLuminance<unsigned short>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_SHORT) {
        expandLuminance<short>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_FLOAT) {
        expandLuminance<float>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else {
        expandLuminance<unsigned char>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      }

      glTexImage<d>(targetV, levelV, GL_RGBA8, widthV, heightV, depthV, borderV, GL_RGBA, typeV, pixelsV3Buffer.get());
    } else if (formatV == GL_LUMINANCE_ALPHA) {
      unique_ptr<char[]> pixelsV3Buffer(new char[widthV * heightV * 4]);

      if (typeV == GL_UNSIGNED_BYTE) {
        expandLuminanceAlpha<unsigned char>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_UNSIGNED_INT) {
        expandLuminanceAlpha<unsigned int>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_INT) {
        expandLuminanceAlpha<int>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_UNSIGNED_SHORT) {
        expandLuminanceAlpha<unsigned short>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_SHORT) {
        expandLuminanceAlpha<short>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else if (typeV == GL_FLOAT) {
        expandLuminanceAlpha<float>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      } else {
        expandLuminanceAlpha<unsigned char>(pixelsV3Buffer.get(), pixelsV2, widthV, heightV);
      }

      glTexImage<d>(targetV, levelV, GL_RGBA8, widthV, heightV, depthV, borderV, GL_RGBA, typeV, pixelsV3Buffer.get());
    } else {
      glTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, formatV, typeV, pixelsV2);
    }

    if (gl->HasPixelStoreiBinding(GL_PACK_ALIGNMENT)) {
      glPixelStorei(GL_PACK_ALIGNMENT, gl->GetPixelStoreiBinding(GL_PACK_ALIGNMENT));
    } else {
      glPixelStorei(GL_PACK_ALIGNMENT, 4);
    }
    if (gl->HasPixelStoreiBinding(GL_UNPACK_ALIGNMENT)) {
      glPixelStorei(GL_UNPACK_ALIGNMENT, gl->GetPixelStoreiBinding(GL_UNPACK_ALIGNMENT));
    } else {
      glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    }
  } else {
    Nan::ThrowError("WebGLRenderingContext::TexImage2D: invalid texture argument");
  }
}

template<int d>
inline void glTexSubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels);
template<>
inline void glTexSubImage<2>(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels) {
  glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
}
template<>
inline void glTexSubImage<3>(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels) {
  glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels);
}
template<int d>
void TexSubImage(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum targetV;
  GLint levelV;
  GLint xoffsetV;
  GLint yoffsetV;
  GLint zoffsetV;
  GLsizei widthV;
  GLsizei heightV;
  GLsizei depthV;
  GLenum formatV;
  GLenum typeV;
  Local<Value> pixels;
  Local<Value> srcOffset;
  if (d == 2) {
    targetV = TO_UINT32(info[0]);
    levelV = TO_INT32(info[1]);
    xoffsetV = TO_INT32(info[2]);
    yoffsetV = TO_INT32(info[3]);
    zoffsetV = 0;
    widthV = TO_UINT32(info[4]);
    heightV = TO_UINT32(info[5]);
    depthV = 0;
    formatV = TO_INT32(info[6]);
    typeV = TO_INT32(info[7]);
    pixels = info[8];
    srcOffset = info[9];
  } else {
    targetV = TO_UINT32(info[0]);
    levelV = TO_INT32(info[1]);
    xoffsetV = TO_INT32(info[2]);
    yoffsetV = TO_INT32(info[3]);
    zoffsetV = TO_INT32(info[4]);
    widthV = TO_UINT32(info[5]);
    heightV = TO_UINT32(info[6]);
    depthV = TO_UINT32(info[7]);
    formatV = TO_INT32(info[8]);
    typeV = TO_INT32(info[9]);
    pixels = info[10];
    srcOffset = info[11];
  }

  if (pixels->IsArrayBufferView() && srcOffset->IsNumber()) {
    Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(pixels);
    size_t srcOffsetInt = TO_UINT32(srcOffset);
    size_t elementSize = getArrayBufferViewElementSize(arrayBufferView);
    size_t extraOffset = srcOffsetInt * elementSize;
    pixels = Uint8Array::New(arrayBufferView->Buffer(), arrayBufferView->ByteOffset() + extraOffset, arrayBufferView->ByteLength() - extraOffset);
  }

  GLuint texV;
  char *pixelsV;
  if (pixels->IsNull()) {
    glTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, typeV, nullptr);
  } else if (pixels->IsNumber()) {
    GLintptr offsetV = TO_UINT32(pixels);
    glTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, typeV, (void *)offsetV);
  } else if ((texV = getImageTexture(pixels)) != 0) {
    GLuint fbos[2];
    glGenFramebuffers(2, fbos);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texV, 0);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, targetV, gl->HasTextureBinding(gl->activeTexture, targetV) ? gl->GetTextureBinding(gl->activeTexture, targetV) : 0, 0);

    const bool flipY = d == 2 && gl->HasPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) ? (bool)gl->GetPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) : false;
    if (flipY) {
      glBlitFramebuffer(
        0, 0, widthV, heightV,
        xoffsetV, yoffsetV, xoffsetV + widthV, yoffsetV + heightV,
        GL_COLOR_BUFFER_BIT,
        GL_NEAREST
      );
    } else {
      glBlitFramebuffer(
        0, heightV, widthV, 0,
        xoffsetV, yoffsetV, xoffsetV + widthV, yoffsetV + heightV,
        GL_COLOR_BUFFER_BIT,
        GL_NEAREST
      );
    }

    // glCopyTexSubImage2D(targetV, levelV, xoffsetV, yoffsetV, 0, 0, widthV, heightV);

    glDeleteFramebuffers(2, fbos);

    if (gl->HasFramebufferBinding(GL_READ_FRAMEBUFFER)) {
      glBindFramebuffer(GL_READ_FRAMEBUFFER, gl->GetFramebufferBinding(GL_READ_FRAMEBUFFER));
    } else {
      glBindFramebuffer(GL_READ_FRAMEBUFFER, gl->defaultFramebuffer);
    }
    if (gl->HasFramebufferBinding(GL_DRAW_FRAMEBUFFER)) {
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gl->GetFramebufferBinding(GL_DRAW_FRAMEBUFFER));
    } else {
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gl->defaultFramebuffer);
    }
  } else if ((pixelsV = (char *)getImageData(pixels)) != nullptr) {
    size_t formatSize = getFormatSize(formatV);
    size_t typeSize = getTypeSize(typeV);
    size_t pixelSize = formatSize * typeSize;
    char *pixelsV2;
    unique_ptr<char[]> pixelsV2Buffer;
    if (formatSize != 4 && !pixels->IsArrayBufferView()) {
      pixelsV2Buffer.reset(new char[widthV * heightV * pixelSize]);
      pixelsV2 = pixelsV2Buffer.get();
      reformatImageData(pixelsV2, pixelsV, formatSize * typeSize, 4 * typeSize, widthV * heightV);
    } else {
      pixelsV2 = pixelsV;
    }

    const bool flipY = d == 2 && gl->HasPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) ? (bool)gl->GetPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) : false;
    if (flipY && !pixels->IsArrayBufferView()) {
      unique_ptr<char[]> pixelsV3Buffer(new char[widthV * heightV * pixelSize]);
      flipImageData(pixelsV3Buffer.get(), pixelsV2, widthV, heightV, pixelSize);

      glTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, typeV, pixelsV3Buffer.get());
    } else {
      glTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, typeV, pixelsV2);
    }
  } else {
    Nan::ThrowError("Invalid texture argument");
  }
}

template<int d>
inline void glCompressedTexImage(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
template<>
inline void glCompressedTexImage<2>(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data) {
  glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data);
}
template<>
inline void glCompressedTexImage<3>(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data) {
  glCompressedTexImage3D(target, level, internalformat, width, height, depth, border, imageSize, data);
}
template<int d>
void CompressedTexImage(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  Local<Value> target;
  Local<Value> level;
  Local<Value> internalformat;
  Local<Value> width;
  Local<Value> height;
  Local<Value> depth;
  Local<Value> border;
  if (d == 2) {
    target = info[0];
    level = info[1];
    internalformat = info[2];
    width = info[3];
    height = info[4];
    border = info[5];
  } else {
    target = info[0];
    level = info[1];
    internalformat = info[2];
    width = info[3];
    height = info[4];
    depth = info[5];
    border = info[6];
  }

  int targetV = TO_INT32(target);
  int levelV = TO_INT32(level);
  int internalformatV = TO_INT32(internalformat);
  int widthV = TO_INT32(width);
  int heightV = TO_INT32(height);
  int depthV = d == 3 ? TO_INT32(depth) : 0;
  int borderV = TO_INT32(border);

  if (d == 2) {
    if (info.Length() == 7) {
      Local<Value> data = info[6];

      char *dataV;
      size_t dataLengthV;
      if (data->IsArrayBufferView()) {
        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(data);
        Local<ArrayBuffer> buffer = arrayBufferView->Buffer();
        dataV = (char *)buffer->GetContents().Data() + arrayBufferView->ByteOffset();
        dataLengthV = arrayBufferView->ByteLength();
      } else if (info[6]->IsNull()) {
        dataV = nullptr;
        dataLengthV = 0;
      } else {
        return Nan::ThrowError("compressedTexImage2D: invalid arguments");
      }

      glCompressedTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, dataLengthV, dataV);
    } else if (info.Length() >= 7) {
      if (info[6]->IsNumber() && info[7]->IsNumber()) {
        Local<Value> imageSize = info[6];
        Local<Value> offset = info[7];

        GLsizei imageSizeV = TO_UINT32(imageSize);
        GLintptr offsetV = (GLintptr)TO_UINT32(offset);

        glCompressedTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, imageSizeV, (GLvoid *)offsetV);
      } else if (info[6]->IsArrayBufferView()) {
        Local<Value> data = info[6];
        Local<Value> srcOffset = info[7];
        Local<Value> srcLengthOverride = info[8];

        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(data);
        Local<ArrayBuffer> buffer = arrayBufferView->Buffer();
        char *dataV = (char *)buffer->GetContents().Data() + arrayBufferView->ByteOffset();
        size_t dataLengthV = arrayBufferView->ByteLength();
        size_t elementSize = getArrayBufferViewElementSize(arrayBufferView);
        if (srcOffset->IsNumber()) {
          GLuint srcOffsetV = TO_UINT32(srcOffset);
          dataV += srcOffsetV * elementSize;
        }
        if (srcLengthOverride->IsNumber()) {
          GLuint srcLengthOverrideV = TO_UINT32(srcLengthOverride);
          size_t newDataLengthV = srcLengthOverrideV * elementSize;
          if (newDataLengthV <= dataLengthV) {
            dataLengthV = newDataLengthV;
          } else {
            return Nan::ThrowError("compressedTexImage2D: invalid srcLengthOverride");
          }
        }

        glCompressedTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, dataLengthV, dataV);
      } else {
        return Nan::ThrowError("compressedTexImage2D: invalid arguments");
      }
    } else {
      return Nan::ThrowError("compressedTexImage2D: invalid arguments");
    }
  } else {
    if (info.Length() >= 8) {
      if (info[7]->IsNumber() && info[8]->IsNumber()) {
        Local<Value> imageSize = info[6];
        Local<Value> offset = info[7];

        GLsizei imageSizeV = TO_UINT32(imageSize);
        GLintptr offsetV = (GLintptr)TO_UINT32(offset);

        glCompressedTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, imageSizeV, (GLvoid *)offsetV);
      } else if (info[7]->IsArrayBufferView()) {
        Local<Value> data = info[7];
        Local<Value> srcOffset = info[8];
        Local<Value> srcLengthOverride = info[9];

        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(data);
        Local<ArrayBuffer> buffer = arrayBufferView->Buffer();
        char *dataV = (char *)buffer->GetContents().Data() + arrayBufferView->ByteOffset();
        size_t dataLengthV = arrayBufferView->ByteLength();
        size_t elementSize = getArrayBufferViewElementSize(arrayBufferView);
        if (srcOffset->IsNumber()) {
          GLuint srcOffsetV = TO_UINT32(srcOffset);
          dataV += srcOffsetV * elementSize;
        }
        if (srcLengthOverride->IsNumber()) {
          GLuint srcLengthOverrideV = TO_UINT32(srcLengthOverride);
          size_t newDataLengthV = srcLengthOverrideV * elementSize;
          if (newDataLengthV <= dataLengthV) {
            dataLengthV = newDataLengthV;
          } else {
            return Nan::ThrowError("compressedTexImage3D: invalid srcLengthOverride");
          }
        }

        glCompressedTexImage<d>(targetV, levelV, internalformatV, widthV, heightV, depthV, borderV, dataLengthV, dataV);
      } else {
        return Nan::ThrowError("compressedTexImage3D: invalid arguments");
      }
    } else {
      return Nan::ThrowError("compressedTexImage3D: invalid arguments");
    }
  }
}

template<int d>
inline void glCompressedTexSubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data);
template<>
inline void glCompressedTexSubImage<2>(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data) {
  glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data);
}
template<>
inline void glCompressedTexSubImage<3>(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data) {
  glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data);
}
template<int d>
void CompressedTexSubImage(const Nan::FunctionCallbackInfo<v8::Value>& info) {
  Local<Value> target;
  Local<Value> level;
  Local<Value> xoffset;
  Local<Value> yoffset;
  Local<Value> zoffset;
  Local<Value> width;
  Local<Value> height;
  Local<Value> depth;
  Local<Value> format;
  if (d == 2) {
    target = info[0];
    level = info[1];
    xoffset = info[2];
    yoffset = info[3];
    width = info[4];
    height = info[5];
    format = info[6];
  } else {
    target = info[0];
    level = info[1];
    xoffset = info[2];
    yoffset = info[3];
    zoffset = info[4];
    width = info[5];
    height = info[6];
    depth = info[7];
    format = info[8];
  }

  int targetV = TO_INT32(target);
  int levelV = TO_INT32(level);
  int xoffsetV = TO_INT32(xoffset);
  int yoffsetV = TO_INT32(yoffset);
  int zoffsetV = d == 3 ? TO_INT32(zoffset) : 0;
  int widthV = TO_INT32(width);
  int heightV = TO_INT32(height);
  int depthV = d == 3 ? TO_INT32(depth) : 0;
  int formatV = TO_INT32(format);

  if (d == 2) {
    if (info.Length() == 8) {
      Local<Value> data = info[7];

      char *dataV;
      size_t dataLengthV;
      if (data->IsArrayBufferView()) {
        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(data);
        Local<ArrayBuffer> buffer = arrayBufferView->Buffer();
        dataV = (char *)buffer->GetContents().Data() + arrayBufferView->ByteOffset();
        dataLengthV = arrayBufferView->ByteLength();
      } else if (info[6]->IsNull()) {
        dataV = nullptr;
        dataLengthV = 0;
      } else {
        return Nan::ThrowError("compressedTexImage2D: invalid arguments");
      }

      glCompressedTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, dataLengthV, dataV);
    } else if (info[7]->IsNumber() && info[8]->IsNumber()) {
      Local<Value> imageSize = info[7];
      Local<Value> offset = info[8];

      GLsizei imageSizeV = TO_UINT32(imageSize);
      GLintptr offsetV = (GLintptr)TO_UINT32(offset);

      glCompressedTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, imageSizeV, (GLvoid *)offsetV);
    } else if (info.Length() >= 8) {
      if (info[7]->IsArrayBufferView()) {
        Local<Value> data = info[7];
        Local<Value> srcOffset = info[8];
        Local<Value> srcLengthOverride = info[9];

        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(data);
        Local<ArrayBuffer> buffer = arrayBufferView->Buffer();
        char *dataV = (char *)buffer->GetContents().Data() + arrayBufferView->ByteOffset();
        size_t dataLengthV = arrayBufferView->ByteLength();
        size_t elementSize = getArrayBufferViewElementSize(arrayBufferView);
        if (srcOffset->IsNumber()) {
          GLuint srcOffsetV = TO_UINT32(srcOffset);
          dataV += srcOffsetV * elementSize;
        }
        if (srcLengthOverride->IsNumber()) {
          GLuint srcLengthOverrideV = TO_UINT32(srcLengthOverride);
          size_t newDataLengthV = srcLengthOverrideV * elementSize;
          if (newDataLengthV <= dataLengthV) {
            dataLengthV = newDataLengthV;
          } else {
            return Nan::ThrowError("compressedTexSubImage2D: invalid srcLengthOverride");
          }
        }

        glCompressedTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, dataLengthV, dataV);
      } else {
        return Nan::ThrowError("compressedTexSubImage2D: invalid arguments");
      }
    } else {
      return Nan::ThrowError("compressedTexSubImage2D: invalid arguments");
    }
  } else {
    if (info.Length() >= 10) {
      if (info[9]->IsNumber() && info[10]->IsNumber()) {
        Local<Value> imageSize = info[9];
        Local<Value> offset = info[10];

        GLsizei imageSizeV = TO_UINT32(imageSize);
        GLintptr offsetV = (GLintptr)TO_UINT32(offset);

        glCompressedTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, imageSizeV, (GLvoid *)offsetV);
      } else if (info[9]->IsArrayBufferView()) {
        Local<Value> data = info[9];
        Local<Value> srcOffset = info[10];
        Local<Value> srcLengthOverride = info[11];

        Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(data);
        Local<ArrayBuffer> buffer = arrayBufferView->Buffer();
        char *dataV = (char *)buffer->GetContents().Data() + arrayBufferView->ByteOffset();
        size_t dataLengthV = arrayBufferView->ByteLength();
        size_t elementSize = getArrayBufferViewElementSize(arrayBufferView);
        if (srcOffset->IsNumber()) {
          GLuint srcOffsetV = TO_UINT32(srcOffset);
          dataV += srcOffsetV * elementSize;
        }
        if (srcLengthOverride->IsNumber()) {
          GLuint srcLengthOverrideV = TO_UINT32(srcLengthOverride);
          size_t newDataLengthV = srcLengthOverrideV * elementSize;
          if (newDataLengthV <= dataLengthV) {
            dataLengthV = newDataLengthV;
          } else {
            return Nan::ThrowError("compressedTexSubImage3D: invalid srcLengthOverride");
          }
        }

        glCompressedTexSubImage<d>(targetV, levelV, xoffsetV, yoffsetV, zoffsetV, widthV, heightV, depthV, formatV, dataLengthV, dataV);
      } else {
        return Nan::ThrowError("compressedTexSubImage3D: invalid arguments");
      }
    } else {
      return Nan::ThrowError("compressedTexSubImage3D: invalid arguments");
    }
  }
}

// initialize

std::pair<Local<Object>, Local<FunctionTemplate>> WebGLRenderingContext::Initialize(Isolate *isolate) {
  #if defined(ANDROID) || defined(LUMIN)
  if(!extensionFunctionsInitialized){
    glFramebufferTextureMultiviewOVRExt = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVR)eglGetProcAddress("glFramebufferTextureMultiviewOVR");
    if (!glFramebufferTextureMultiviewOVRExt) {
        std::cerr << "Can not get proc address for glFramebufferTextureMultiviewOVR." << std::endl;
    }
    glFramebufferTextureMultisampleMultiviewOVRExt = (PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVR)eglGetProcAddress("glFramebufferTextureMultisampleMultiviewOVR");
    if (!glFramebufferTextureMultisampleMultiviewOVRExt) {
        std::cerr << "Can not get proc address for glFramebufferTextureMultisampleMultiviewOVRExt." << std::endl;
    }
    extensionFunctionsInitialized = true;
  }
  #endif

  // constructor
  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(WebGLRenderingContext::New);

  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(JS_STR("WebGLRenderingContext"));

  // prototype
  Local<ObjectTemplate> proto = ctor->PrototypeTemplate();

  Nan::SetMethod(proto, "destroy", Destroy);
  Nan::SetMethod(proto, "getWindowHandle", GetWindowHandle);
  Nan::SetMethod(proto, "setWindowHandle", SetWindowHandle);
  Nan::SetMethod(proto, "setDefaultVao", SetDefaultVao);
  Nan::SetMethod(proto, "isDirty", IsDirty);
  Nan::SetMethod(proto, "clearDirty", ClearDirty);

  Nan::SetMethod(proto, "uniform1f", glCallWrap<Uniform1f>);
  Nan::SetMethod(proto, "uniform2f", glCallWrap<Uniform2f>);
  Nan::SetMethod(proto, "uniform3f", glCallWrap<Uniform3f>);
  Nan::SetMethod(proto, "uniform4f", glCallWrap<Uniform4f>);
  Nan::SetMethod(proto, "uniform1i", glCallWrap<Uniform1i>);
  Nan::SetMethod(proto, "uniform2i", glCallWrap<Uniform2i>);
  Nan::SetMethod(proto, "uniform3i", glCallWrap<Uniform3i>);
  Nan::SetMethod(proto, "uniform4i", glCallWrap<Uniform4i>);
  Nan::SetMethod(proto, "uniform1ui", glCallWrap<Uniform1ui>);
  Nan::SetMethod(proto, "uniform2ui", glCallWrap<Uniform2ui>);
  Nan::SetMethod(proto, "uniform3ui", glCallWrap<Uniform3ui>);
  Nan::SetMethod(proto, "uniform4ui", glCallWrap<Uniform4ui>);
  Nan::SetMethod(proto, "uniform1fv", glCallWrap<Uniform1fv>);
  Nan::SetMethod(proto, "uniform2fv", glCallWrap<Uniform2fv>);
  Nan::SetMethod(proto, "uniform3fv", glCallWrap<Uniform3fv>);
  Nan::SetMethod(proto, "uniform4fv", glCallWrap<Uniform4fv>);
  Nan::SetMethod(proto, "uniform1iv", glCallWrap<Uniform1iv>);
  Nan::SetMethod(proto, "uniform2iv", glCallWrap<Uniform2iv>);
  Nan::SetMethod(proto, "uniform3iv", glCallWrap<Uniform3iv>);
  Nan::SetMethod(proto, "uniform4iv", glCallWrap<Uniform4iv>);

  Nan::SetMethod(proto, "uniform1uiv", glCallWrap<Uniform1uiv>);
  Nan::SetMethod(proto, "uniform2uiv", glCallWrap<Uniform2uiv>);
  Nan::SetMethod(proto, "uniform3uiv", glCallWrap<Uniform3uiv>);
  Nan::SetMethod(proto, "uniform4uiv", glCallWrap<Uniform4uiv>);
  Nan::SetMethod(proto, "uniformMatrix2fv", glCallWrap<UniformMatrix2fv>);
  Nan::SetMethod(proto, "uniformMatrix3fv", glCallWrap<UniformMatrix3fv>);
  Nan::SetMethod(proto, "uniformMatrix4fv", glCallWrap<UniformMatrix4fv>);
  Nan::SetMethod(proto, "uniformMatrix3x2fv", glCallWrap<UniformMatrix3x2fv>);
  Nan::SetMethod(proto, "uniformMatrix4x2fv", glCallWrap<UniformMatrix4x2fv>);
  Nan::SetMethod(proto, "uniformMatrix2x3fv", glCallWrap<UniformMatrix2x3fv>);
  Nan::SetMethod(proto, "uniformMatrix4x3fv", glCallWrap<UniformMatrix4x3fv>);
  Nan::SetMethod(proto, "uniformMatrix2x4fv", glCallWrap<UniformMatrix2x4fv>);
  Nan::SetMethod(proto, "uniformMatrix3x4fv", glCallWrap<UniformMatrix3x4fv>);

  Nan::SetMethod(proto, "pixelStorei", glCallWrap<PixelStorei>);
  Nan::SetMethod(proto, "bindAttribLocation", glCallWrap<BindAttribLocation>);
  Nan::SetMethod(proto, "getError", glCallWrap<GetError>);
  Nan::SetMethod(proto, "drawArrays", glCallWrap<DrawArrays>);
  Nan::SetMethod(proto, "drawArraysInstanced", glCallWrap<DrawArraysInstanced>);
  Nan::SetMethod(proto, "drawArraysInstancedANGLE", glCallWrap<DrawArraysInstancedANGLE>);

  Nan::SetMethod(proto, "generateMipmap", glCallWrap<GenerateMipmap>);

  Nan::SetMethod(proto, "getAttribLocation", glCallWrap<GetAttribLocation>);
  Nan::SetMethod(proto, "depthFunc", glCallWrap<DepthFunc>);
  Nan::SetMethod(proto, "viewport", glCallWrap<Viewport>);
  Nan::SetMethod(proto, "createShader", glCallWrap<CreateShader>);
  Nan::SetMethod(proto, "shaderSource", glCallWrap<ShaderSource>);
  Nan::SetMethod(proto, "compileShader", glCallWrap<CompileShader>);
  Nan::SetMethod(proto, "getShaderParameter", glCallWrap<GetShaderParameter>);
  Nan::SetMethod(proto, "getShaderInfoLog", glCallWrap<GetShaderInfoLog>);
  Nan::SetMethod(proto, "createProgram", glCallWrap<CreateProgram>);
  Nan::SetMethod(proto, "attachShader", glCallWrap<AttachShader>);
  Nan::SetMethod(proto, "linkProgram", glCallWrap<LinkProgram>);
  Nan::SetMethod(proto, "getProgramParameter", glCallWrap<GetProgramParameter>);
  Nan::SetMethod(proto, "getUniformLocation", glCallWrap<GetUniformLocation>);
  Nan::SetMethod(proto, "getUniformIndices", glCallWrap<GetUniformIndices>);
  Nan::SetMethod(proto, "getActiveUniforms", glCallWrap<GetActiveUniforms>);
  Nan::SetMethod(proto, "getUniformBlockIndex", glCallWrap<GetUniformBlockIndex>);
  Nan::SetMethod(proto, "uniformBlockBinding", glCallWrap<UniformBlockBinding>);
  Nan::SetMethod(proto, "getActiveUniformBlockName", glCallWrap<GetActiveUniformBlockName>);
  Nan::SetMethod(proto, "getActiveUniformBlockParameter", glCallWrap<GetActiveUniformBlockParameter>);
  Nan::SetMethod(proto, "getUniform", glCallWrap<GetUniform>);
  Nan::SetMethod(proto, "getIndexedParameter", glCallWrap<GetIndexedParameter>);
  Nan::SetMethod(proto, "getFragDataLocation", glCallWrap<GetFragDataLocation>);
  Nan::SetMethod(proto, "clearColor", glCallWrap<ClearColor>);
  Nan::SetMethod(proto, "clearDepth", glCallWrap<ClearDepth>);

  Nan::SetMethod(proto, "disable", glCallWrap<Disable>);
  Nan::SetMethod(proto, "createTexture", glCallWrap<CreateTexture>);
  Nan::SetMethod(proto, "bindTexture", glCallWrap<BindTexture>);
  // Nan::SetMethod(proto, "flipTextureData", glCallWrap<FlipTextureData>);
  Nan::SetMethod(proto, "texImage2D", glCallWrap<TexImage<2>>);
  Nan::SetMethod(proto, "texImage3D", glCallWrap<TexImage<3>>);
  Nan::SetMethod(proto, "compressedTexImage2D", glCallWrap<CompressedTexImage<2>>);
  Nan::SetMethod(proto, "compressedTexImage3D", glCallWrap<CompressedTexImage<3>>);
  Nan::SetMethod(proto, "compressedTexSubImage2D", glCallWrap<CompressedTexSubImage<2>>);
  Nan::SetMethod(proto, "compressedTexSubImage3D", glCallWrap<CompressedTexSubImage<3>>);
  Nan::SetMethod(proto, "texParameteri", glCallWrap<TexParameteri>);
  Nan::SetMethod(proto, "texParameterf", glCallWrap<TexParameterf>);
  Nan::SetMethod(proto, "clear", glCallWrap<Clear>);
  Nan::SetMethod(proto, "useProgram", glCallWrap<UseProgram>);
  Nan::SetMethod(proto, "createFramebuffer", glCallWrap<CreateFramebuffer>);
  Nan::SetMethod(proto, "bindFramebuffer", glCallWrap<BindFramebuffer>);
  Nan::SetMethod(proto, "bindFramebufferRaw", glCallWrap<BindFramebufferRaw>);
  Nan::SetMethod(proto, "framebufferTexture2D", glCallWrap<FramebufferTexture2D>);
  Nan::SetMethod(proto, "framebufferTextureLayer", glCallWrap<FramebufferTextureLayer>);
  Nan::SetMethod(proto, "blitFramebuffer", glCallWrap<BlitFramebuffer>);
  Nan::SetMethod(proto, "invalidateFramebuffer", glCallWrap<InvalidateFramebuffer>);
  Nan::SetMethod(proto, "invalidateSubFramebuffer", glCallWrap<InvalidateSubFramebuffer>);
  Nan::SetMethod(proto, "createBuffer", glCallWrap<CreateBuffer>);
  Nan::SetMethod(proto, "bindBuffer", glCallWrap<BindBuffer>);
  Nan::SetMethod(proto, "bindBufferBase", glCallWrap<BindBufferBase>);
  Nan::SetMethod(proto, "bindBufferRange", glCallWrap<BindBufferRange>);
  Nan::SetMethod(proto, "bufferData", glCallWrap<BufferData>);
  Nan::SetMethod(proto, "bufferSubData", glCallWrap<BufferSubData>);
  Nan::SetMethod(proto, "copyBufferSubData", glCallWrap<CopyBufferSubData>);
  Nan::SetMethod(proto, "getBufferSubData", glCallWrap<GetBufferSubData>);
  Nan::SetMethod(proto, "readBuffer", glCallWrap<ReadBuffer>);
  Nan::SetMethod(proto, "enable", glCallWrap<Enable>);
  Nan::SetMethod(proto, "blendEquation", glCallWrap<BlendEquation>);
  Nan::SetMethod(proto, "blendFunc", glCallWrap<BlendFunc>);
  Nan::SetMethod(proto, "enableVertexAttribArray", glCallWrap<EnableVertexAttribArray>);
  Nan::SetMethod(proto, "vertexAttribPointer", glCallWrap<VertexAttribPointer>);
  Nan::SetMethod(proto, "vertexAttribIPointer", glCallWrap<VertexAttribIPointer>);
  Nan::SetMethod(proto, "activeTexture", glCallWrap<ActiveTexture>);
  Nan::SetMethod(proto, "drawElements", glCallWrap<DrawElements>);
  Nan::SetMethod(proto, "drawElementsInstanced", glCallWrap<DrawElementsInstanced>);
  Nan::SetMethod(proto, "drawElementsInstancedANGLE", glCallWrap<DrawElementsInstancedANGLE>);
  Nan::SetMethod(proto, "drawRangeElements", glCallWrap<DrawRangeElements>);
  Nan::SetMethod(proto, "flush", glCallWrap<Flush>);
  Nan::SetMethod(proto, "finish", glCallWrap<Finish>);

  Nan::SetMethod(proto, "vertexAttrib1f", glCallWrap<VertexAttrib1f>);
  Nan::SetMethod(proto, "vertexAttrib2f", glCallWrap<VertexAttrib2f>);
  Nan::SetMethod(proto, "vertexAttrib3f", glCallWrap<VertexAttrib3f>);
  Nan::SetMethod(proto, "vertexAttrib4f", glCallWrap<VertexAttrib4f>);
  Nan::SetMethod(proto, "vertexAttrib1fv", glCallWrap<VertexAttrib1fv>);
  Nan::SetMethod(proto, "vertexAttrib2fv", glCallWrap<VertexAttrib2fv>);
  Nan::SetMethod(proto, "vertexAttrib3fv", glCallWrap<VertexAttrib3fv>);
  Nan::SetMethod(proto, "vertexAttrib4fv", glCallWrap<VertexAttrib4fv>);

  Nan::SetMethod(proto, "vertexAttribI4i", glCallWrap<VertexAttribI4i>);
  Nan::SetMethod(proto, "vertexAttribI4iv", glCallWrap<VertexAttribI4iv>);
  Nan::SetMethod(proto, "vertexAttribI4ui", glCallWrap<VertexAttribI4ui>);
  Nan::SetMethod(proto, "vertexAttribI4uiv", glCallWrap<VertexAttribI4uiv>);

  Nan::SetMethod(proto, "vertexAttribDivisor", glCallWrap<VertexAttribDivisor>);
  Nan::SetMethod(proto, "vertexAttribDivisorANGLE", glCallWrap<VertexAttribDivisorANGLE>);
  Nan::SetMethod(proto, "drawBuffers", glCallWrap<DrawBuffers>);
  Nan::SetMethod(proto, "drawBuffersWEBGL", glCallWrap<DrawBuffersWEBGL>);
  Nan::SetMethod(proto, "clearBufferfv", glCallWrap<ClearBufferfv>);
  Nan::SetMethod(proto, "clearBufferiv", glCallWrap<ClearBufferiv>);
  Nan::SetMethod(proto, "clearBufferuiv", glCallWrap<ClearBufferuiv>);
  Nan::SetMethod(proto, "clearBufferfi", glCallWrap<ClearBufferfi>);

  Nan::SetMethod(proto, "blendColor", glCallWrap<BlendColor>);
  Nan::SetMethod(proto, "blendEquationSeparate", glCallWrap<BlendEquationSeparate>);
  Nan::SetMethod(proto, "blendFuncSeparate", glCallWrap<BlendFuncSeparate>);
  Nan::SetMethod(proto, "clearStencil", glCallWrap<ClearStencil>);
  Nan::SetMethod(proto, "colorMask", glCallWrap<ColorMask>);
  Nan::SetMethod(proto, "copyTexImage2D", glCallWrap<CopyTexImage2D>);
  Nan::SetMethod(proto, "copyTexSubImage2D", glCallWrap<CopyTexSubImage2D>);
  Nan::SetMethod(proto, "cullFace", glCallWrap<CullFace>);
  Nan::SetMethod(proto, "depthMask", glCallWrap<DepthMask>);
  Nan::SetMethod(proto, "depthRange", glCallWrap<DepthRange>);
  Nan::SetMethod(proto, "disableVertexAttribArray", glCallWrap<DisableVertexAttribArray>);
  Nan::SetMethod(proto, "hint", glCallWrap<Hint>);
  Nan::SetMethod(proto, "isEnabled", glCallWrap<IsEnabled>);
  Nan::SetMethod(proto, "lineWidth", glCallWrap<LineWidth>);
  Nan::SetMethod(proto, "polygonOffset", glCallWrap<PolygonOffset>);

  Nan::SetMethod(proto, "scissor", glCallWrap<Scissor>);
  Nan::SetMethod(proto, "stencilFunc", glCallWrap<StencilFunc>);
  Nan::SetMethod(proto, "stencilFuncSeparate", glCallWrap<StencilFuncSeparate>);
  Nan::SetMethod(proto, "stencilMask", glCallWrap<StencilMask>);
  Nan::SetMethod(proto, "stencilMaskSeparate", glCallWrap<StencilMaskSeparate>);
  Nan::SetMethod(proto, "stencilOp", glCallWrap<StencilOp>);
  Nan::SetMethod(proto, "stencilOpSeparate", glCallWrap<StencilOpSeparate>);
  Nan::SetMethod(proto, "bindRenderbuffer", glCallWrap<BindRenderbuffer>);
  Nan::SetMethod(proto, "createRenderbuffer", glCallWrap<CreateRenderbuffer>);

  Nan::SetMethod(proto, "deleteBuffer", glCallWrap<DeleteBuffer>);
  Nan::SetMethod(proto, "deleteFramebuffer", glCallWrap<DeleteFramebuffer>);
  Nan::SetMethod(proto, "deleteProgram", glCallWrap<DeleteProgram>);
  Nan::SetMethod(proto, "deleteRenderbuffer", glCallWrap<DeleteRenderbuffer>);
  Nan::SetMethod(proto, "deleteShader", glCallWrap<DeleteShader>);
  Nan::SetMethod(proto, "deleteTexture", glCallWrap<DeleteTexture>);
  Nan::SetMethod(proto, "detachShader", glCallWrap<DetachShader>);
  Nan::SetMethod(proto, "framebufferRenderbuffer", glCallWrap<FramebufferRenderbuffer>);
  Nan::SetMethod(proto, "getVertexAttribOffset", glCallWrap<GetVertexAttribOffset>);
  Nan::SetMethod(proto, "getShaderPrecisionFormat", glCallWrap<GetShaderPrecisionFormat>);

  Nan::SetMethod(proto, "isBuffer", glCallWrap<IsBuffer>);
  Nan::SetMethod(proto, "isFramebuffer", glCallWrap<IsFramebuffer>);
  Nan::SetMethod(proto, "isProgram", glCallWrap<IsProgram>);
  Nan::SetMethod(proto, "isRenderbuffer", glCallWrap<IsRenderbuffer>);
  Nan::SetMethod(proto, "isShader", glCallWrap<IsShader>);
  Nan::SetMethod(proto, "isTexture", glCallWrap<IsTexture>);
  Nan::SetMethod(proto, "isVertexArray", glCallWrap<IsVertexArray>);
  Nan::SetMethod(proto, "isSync", glCallWrap<IsSync>);

  Nan::SetMethod(proto, "renderbufferStorage", glCallWrap<RenderbufferStorage>);
  Nan::SetMethod(proto, "renderbufferStorageMultisample", glCallWrap<RenderbufferStorageMultisample>);
  Nan::SetMethod(proto, "getShaderSource", glCallWrap<GetShaderSource>);
  Nan::SetMethod(proto, "validateProgram", glCallWrap<ValidateProgram>);

  Nan::SetMethod(proto, "texSubImage2D", glCallWrap<TexSubImage<2>>);
  Nan::SetMethod(proto, "texSubImage3D", glCallWrap<TexSubImage<3>>);
  Nan::SetMethod(proto, "texStorage2D", glCallWrap<TexStorage2D>);
  Nan::SetMethod(proto, "texStorage3D", glCallWrap<TexStorage3D>);

  Nan::SetMethod(proto, "readPixels", glCallWrap<ReadPixels>);
  Nan::SetMethod(proto, "getTexParameter", glCallWrap<GetTexParameter>);
  Nan::SetMethod(proto, "getActiveAttrib", glCallWrap<GetActiveAttrib>);
  Nan::SetMethod(proto, "getActiveUniform", glCallWrap<GetActiveUniform>);
  Nan::SetMethod(proto, "getAttachedShaders", glCallWrap<GetAttachedShaders>);
  Nan::SetMethod(proto, "getParameter", glCallWrap<GetParameter>);
  Nan::SetMethod(proto, "getBufferParameter", glCallWrap<GetBufferParameter>);
  Nan::SetMethod(proto, "getFramebufferAttachmentParameter", glCallWrap<GetFramebufferAttachmentParameter>);
  Nan::SetMethod(proto, "getProgramInfoLog", glCallWrap<GetProgramInfoLog>);
  Nan::SetMethod(proto, "getRenderbufferParameter", glCallWrap<GetRenderbufferParameter>);
  Nan::SetMethod(proto, "getVertexAttrib", glCallWrap<GetVertexAttrib>);
  Nan::SetMethod(proto, "getFragDataLocation", glCallWrap<GetFragDataLocation>);
  Nan::SetMethod(proto, "getSupportedExtensions", glCallWrap<GetSupportedExtensions>);
  Nan::SetMethod(proto, "getExtension", glCallWrap<GetExtension>);
  // Nan::SetMethod(proto, "getContextAttributes", glCallWrap<GetContextAttributes>);

  Nan::SetMethod(proto, "checkFramebufferStatus", glCallWrap<CheckFramebufferStatus>);

  Nan::SetMethod(proto, "createVertexArray", glCallWrap<CreateVertexArray>);
  Nan::SetMethod(proto, "deleteVertexArray", glCallWrap<DeleteVertexArray>);
  Nan::SetMethod(proto, "bindVertexArray", glCallWrap<BindVertexArray>);

  Nan::SetMethod(proto, "fenceSync", glCallWrap<FenceSync>);
  Nan::SetMethod(proto, "deleteSync", glCallWrap<DeleteSync>);
  Nan::SetMethod(proto, "clientWaitSync", glCallWrap<ClientWaitSync>);
  Nan::SetMethod(proto, "waitSync", glCallWrap<WaitSync>);
  Nan::SetMethod(proto, "getSyncParameter", glCallWrap<GetSyncParameter>);

  Nan::SetMethod(proto, "frontFace", glCallWrap<FrontFace>);

  Nan::SetMethod(proto, "isContextLost", glCallWrap<IsContextLost>);

  Nan::SetAccessor(proto, JS_STR("drawingBufferWidth"), glGetterWrap<DrawingBufferWidthGetter>);
  Nan::SetAccessor(proto, JS_STR("drawingBufferHeight"), glGetterWrap<DrawingBufferHeightGetter>);

  // OVR_multiview2
  Nan::SetMethod(proto, "framebufferTextureMultiviewOVR", glCallWrap<FramebufferTextureMultiviewOVR>);
  Nan::SetMethod(proto, "framebufferTextureMultisampleMultiviewOVR", glCallWrap<FramebufferTextureMultisampleMultiviewOVR>);

  setGlConstants(proto);
  
  // non-standard

  Nan::SetMethod(proto, "getBoundFramebuffer", glCallWrap<GetBoundFramebuffer>);
  Nan::SetMethod(proto, "getDefaultFramebuffer", GetDefaultFramebuffer);
  Nan::SetMethod(proto, "setDefaultFramebuffer", glCallWrap<SetDefaultFramebuffer>);
  Nan::SetMethod(proto, "setClearEnabled", SetClearEnabled);
  Nan::SetMethod(proto, "loadSubTexture", LoadSubTexture);

  // ctor
  Local<Function> ctorFn = Nan::GetFunction(ctor).ToLocalChecked();
  setGlConstants(ctorFn);

  return std::pair<Local<Object>, Local<FunctionTemplate>>(ctorFn, ctor);
}

WebGLRenderingContext::WebGLRenderingContext() :
  live(true),
  windowHandle(nullptr),
  defaultVao(0),
  defaultFramebuffer(0),
  clearEnabled(true),
  dirty(false),
  activeTexture(GL_TEXTURE0)
  {}

WebGLRenderingContext::~WebGLRenderingContext() {
  for (auto iter = keys.begin(); iter != keys.end(); iter++) {
    GlShader *glShader = (GlShader *)iter->second;
    delete glShader;
  }
}

NAN_METHOD(WebGLRenderingContext::New) {
  WebGLRenderingContext *gl = new WebGLRenderingContext();
  Local<Object> glObj = info.This();
  gl->Wrap(glObj);

  info.GetReturnValue().Set(glObj);
}

NAN_METHOD(WebGLRenderingContext::Destroy) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  gl->live = false;

  for (auto iter = gl->objectCache.buffers.begin(); iter != gl->objectCache.buffers.end(); iter++) {
    GLuint buffer = *iter;
    glDeleteBuffers(1, &buffer);
  }
  for (auto iter = gl->objectCache.queries.begin(); iter != gl->objectCache.queries.end(); iter++) {
    GLuint query = *iter;
    glDeleteQueries(1, &query);
  }
  for (auto iter = gl->objectCache.renderbuffers.begin(); iter != gl->objectCache.renderbuffers.end(); iter++) {
    GLuint renderbuffer = *iter;
    glDeleteRenderbuffers(1, &renderbuffer);
  }
  for (auto iter = gl->objectCache.samplers.begin(); iter != gl->objectCache.samplers.end(); iter++) {
    GLuint sampler = *iter;
    glDeleteSamplers(1, &sampler);
  }
  for (auto iter = gl->objectCache.textures.begin(); iter != gl->objectCache.textures.end(); iter++) {
    GLuint texture = *iter;
    glDeleteTextures(1, &texture);
  }
}

NAN_METHOD(WebGLRenderingContext::GetWindowHandle) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  if (gl->windowHandle) {
    info.GetReturnValue().Set(pointerToArray(gl->windowHandle));
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGLRenderingContext::SetWindowHandle) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  if (info[0]->IsArray()) {
    gl->windowHandle = (NATIVEwindow *)arrayToPointer(Local<Array>::Cast(info[0]));
  } else {
    gl->windowHandle = nullptr;
  }
}

NAN_METHOD(WebGLRenderingContext::SetDefaultVao) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  gl->defaultVao = TO_UINT32(info[0]);
}

NAN_METHOD(WebGLRenderingContext::IsDirty) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  info.GetReturnValue().Set(JS_BOOL(gl->dirty));
}

NAN_METHOD(WebGLRenderingContext::ClearDirty) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  gl->dirty = false;
}

// GL CALLS

NAN_METHOD(WebGLRenderingContext::Uniform1f) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    float x = TO_FLOAT(info[1]);

    glUniform1f(location, x);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform2f) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    float x = TO_FLOAT(info[1]);
    float y = TO_FLOAT(info[2]);

    glUniform2f(location, x, y);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform3f) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    float x = TO_FLOAT(info[1]);
    float y = TO_FLOAT(info[2]);
    float z = TO_FLOAT(info[3]);

    glUniform3f(location, x, y, z);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform4f) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    float x = TO_FLOAT(info[1]);
    float y = TO_FLOAT(info[2]);
    float z = TO_FLOAT(info[3]);
    float w = TO_FLOAT(info[4]);

    glUniform4f(location, x, y, z, w);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform1i) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLint x = TO_INT32(info[1]);

    glUniform1i(location, x);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform2i) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLint x = TO_INT32(info[1]);
    GLint y = TO_INT32(info[2]);

    glUniform2i(location, x, y);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform3i) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLint x = TO_INT32(info[1]);
    GLint y = TO_INT32(info[2]);
    GLint z = TO_INT32(info[3]);

    glUniform3i(location, x, y, z);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform4i) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLint x = TO_INT32(info[1]);
    GLint y = TO_INT32(info[2]);
    GLint z = TO_INT32(info[3]);
    GLint w = TO_INT32(info[4]);

    glUniform4i(location, x, y, z, w);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform1ui) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLuint x = TO_UINT32(info[1]);

    glUniform1ui(location, x);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform2ui) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLuint x = TO_UINT32(info[1]);
    GLuint y = TO_UINT32(info[2]);

    glUniform2ui(location, x, y);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform3ui) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLuint x = TO_UINT32(info[1]);
    GLuint y = TO_UINT32(info[2]);
    GLuint z = TO_UINT32(info[3]);

    glUniform3ui(location, x, y, z);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform4ui) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLuint x = TO_UINT32(info[1]);
    GLuint y = TO_UINT32(info[2]);
    GLuint z = TO_UINT32(info[3]);
    GLuint w = TO_UINT32(info[4]);

    glUniform4ui(location, x, y, z, w);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform1fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLfloat *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
    } else {
      data = getArrayData<GLfloat>(info[1], &count);
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform1fv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform2fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLfloat *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 2;
    } else {
      data = getArrayData<GLfloat>(info[1], &count);
      count /= 2;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 2;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 2;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform2fv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform3fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLfloat *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 3;
    } else {
      data = getArrayData<GLfloat>(info[1], &count);
      count /= 3;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 3;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 3;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform3fv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform4fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLfloat *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 4;
    } else {
      data = getArrayData<GLfloat>(info[1], &count);
      count /= 4;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 4;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 4;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform4fv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform1iv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLint *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Int32Array> int32Array = Int32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        int32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLint>(int32Array, &count);
    } else {
      data = getArrayData<GLint>(info[1], &count);
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform1iv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform2iv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLint *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Int32Array> int32Array = Int32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        int32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLint>(int32Array, &count);
      count /= 2;
    } else {
      data = getArrayData<GLint>(info[1], &count);
      count /= 2;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 2;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 2;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform2iv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform3iv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLint *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Int32Array> int32Array = Int32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        int32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLint>(int32Array, &count);
      count /= 3;
    } else {
      data = getArrayData<GLint>(info[1], &count);
      count /= 3;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 3;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 3;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform3iv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform4iv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

    GLint *data;
    int count;
    if (info[1]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[1]);
      unsigned int length = array->Length();
      Local<Int32Array> int32Array = Int32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        int32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLint>(int32Array, &count);
      count /= 4;
    } else {
      data = getArrayData<GLint>(info[1], &count);
      count /= 4;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 4;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 4;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform4iv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform1uiv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    Local<Value> dataValue = info[1];

    GLuint *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Uint32Array> uint32Array = Uint32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        uint32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLuint>(uint32Array, &count);
    } else {
      data = getArrayData<GLuint>(dataValue, &count);
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform1uiv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform2uiv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    Local<Value> dataValue = info[1];

    GLuint *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Uint32Array> uint32Array = Uint32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        uint32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLuint>(uint32Array, &count);
      count /= 2;
    } else {
      data = getArrayData<GLuint>(dataValue, &count);
      count /= 2;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 2;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 2;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform2uiv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform3uiv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    Local<Value> dataValue = info[1];

    GLuint *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Uint32Array> uint32Array = Uint32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        uint32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLuint>(uint32Array, &count);
      count /= 3;
    } else {
      data = getArrayData<GLuint>(dataValue, &count);
      count /= 3;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 3;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 3;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform3uiv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::Uniform4uiv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    Local<Value> dataValue = info[1];

    GLuint *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Uint32Array> uint32Array = Uint32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        uint32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLuint>(uint32Array, &count);
      count /= 4;
    } else {
      data = getArrayData<GLuint>(dataValue, &count);
      count /= 4;
    }
    if (info[2]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[2]);
      data += srcOffset;
      count -= srcOffset / 4;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[3]);
      srcLength /= 4;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniform4uiv(location, count, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix2fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLboolean transpose = TO_BOOL(info[1]);

    GLfloat *data;
    int count;
    if (info[2]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[2]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 2*2;
    } else {
      data = getArrayData<GLfloat>(info[2], &count);
      count /= 2*2;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (2*2);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 2*2;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix2fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix3fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLboolean transpose = TO_BOOL(info[1]);

    GLfloat *data;
    int count;
    if (info[2]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[2]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 3*3;
    } else {
      data = getArrayData<GLfloat>(info[2], &count);
      count /= 3*3;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (3*3);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 3*3;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix3fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix4fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    GLboolean transpose = TO_BOOL(info[1]);

    GLfloat *data;
    int count;
    if (info[2]->IsArray()) {
      Local<Array> array = Local<Array>::Cast(info[2]);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 4*4;
    } else {
      data = getArrayData<GLfloat>(info[2], &count);
      count /= 4*4;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (4*4);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 4*4;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix4fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix3x2fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    bool transpose = TO_BOOL(info[1]);
    Local<Value> dataValue = info[2];

    GLfloat *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 3*2;
    } else {
      data = getArrayData<GLfloat>(dataValue, &count);
      count /= 3*2;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (3*2);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 3*2;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix3x2fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix4x2fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    bool transpose = TO_BOOL(info[1]);
    Local<Value> dataValue = info[2];

    GLfloat *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 4*2;
    } else {
      data = getArrayData<GLfloat>(dataValue, &count);
      count /= 4*2;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (4*2);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 4*2;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix4x2fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix2x3fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    bool transpose = TO_BOOL(info[1]);
    Local<Value> dataValue = info[2];

    GLfloat *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 2*3;
    } else {
      data = getArrayData<GLfloat>(dataValue, &count);
      count /= 2*3;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (2*3);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 2*3;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix2x3fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix4x3fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    bool transpose = TO_BOOL(info[1]);
    Local<Value> dataValue = info[2];

    GLfloat *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 4*3;
    } else {
      data = getArrayData<GLfloat>(dataValue, &count);
      count /= 4*3;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (4*3);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 4*3;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix4x3fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix2x4fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    bool transpose = TO_BOOL(info[1]);
    Local<Value> dataValue = info[2];

    GLfloat *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 2*4;
    } else {
      data = getArrayData<GLfloat>(dataValue, &count);
      count /= 2*4;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (2*4);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 2*4;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix2x4fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::UniformMatrix3x4fv) {
  if (info[0]->IsObject()) {
    GLuint location = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
    bool transpose = TO_BOOL(info[1]);
    Local<Value> dataValue = info[2];

    GLfloat *data;
    int count;
    if (dataValue->IsArray()) {
      Local<Array> array = Local<Array>::Cast(dataValue);
      unsigned int length = array->Length();
      Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
      for (unsigned int i = 0; i < length; i++) {
        float32Array->Set(i, array->Get(i));
      }
      data = getArrayData<GLfloat>(float32Array, &count);
      count /= 3*4;
    } else {
      data = getArrayData<GLfloat>(dataValue, &count);
      count /= 3*4;
    }
    if (info[3]->IsNumber()) {
      GLsizei srcOffset = TO_UINT32(info[3]);
      data += srcOffset;
      count -= srcOffset / (3*4);
    }
    if (info[4]->IsNumber()) {
      GLsizei srcLength = TO_UINT32(info[4]);
      srcLength /= 3*4;
      count = std::min<GLsizei>(srcLength, count);
    }

    glUniformMatrix3x4fv(location, count, transpose, data);
  }
}

NAN_METHOD(WebGLRenderingContext::PixelStorei) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  int pname = TO_INT32(info[0]);
  int param = TO_INT32(info[1]);

  if (pname == UNPACK_FLIP_Y_WEBGL || pname == UNPACK_PREMULTIPLY_ALPHA_WEBGL) {
    // nothing; tracked internally
  } else {
    glPixelStorei(pname, param);
  }
  
  gl->SetPixelStoreiBinding(pname, param);
}

NAN_METHOD(WebGLRenderingContext::BindAttribLocation) {
  GLuint programId = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  int index = TO_INT32(info[1]);
  Nan::Utf8String name(Local<String>::Cast(info[2]));

  glBindAttribLocation(programId, index, *name);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::GetError) {
  GLenum error = glGetError();
  info.GetReturnValue().Set(Nan::New<Integer>(error));
}


NAN_METHOD(WebGLRenderingContext::DrawArrays) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  int mode = TO_INT32(info[0]);
  int first = TO_INT32(info[1]);
  int count = TO_INT32(info[2]);

  glDrawArrays(mode, first, count);

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DrawArraysInstanced) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  int mode = TO_INT32(info[0]);
  int first = TO_INT32(info[1]);
  int count = TO_INT32(info[2]);
  int primcount = TO_INT32(info[3]);

  glDrawArraysInstanced(mode, first, count, primcount);

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DrawArraysInstancedANGLE) {
  Local<Object> contextObj = Local<Object>::Cast(info.This()->Get(JS_STR("context")));
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(contextObj);
  int mode = TO_INT32(info[0]);
  int first = TO_INT32(info[1]);
  int count = TO_INT32(info[2]);
  int primcount = TO_INT32(info[3]);

  glDrawArraysInstanced(mode, first, count, primcount);

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::GenerateMipmap) {
  GLint target = TO_INT32(info[0]);
  glGenerateMipmap(target);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::GetAttribLocation) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Nan::Utf8String name(Local<String>::Cast(info[1]));

  GLint result = glGetAttribLocation(programId, *name);

  info.GetReturnValue().Set(Nan::New<Number>(result));
}


NAN_METHOD(WebGLRenderingContext::DepthFunc) {
  GLint arg = TO_INT32(info[0]);
  glDepthFunc(arg);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::Viewport) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLint x = TO_INT32(info[0]);
  GLint y = TO_INT32(info[1]);
  GLsizei width = TO_INT32(info[2]);
  GLsizei height = TO_INT32(info[3]);

  glViewport(x, y, width, height);

  gl->viewportState = ViewportState(x, y, width, height);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::CreateShader) {
  GLint type = TO_INT32(info[0]);

  GLuint shaderId = glCreateShader(type);
  Local<Object> shaderObject = Nan::New<Object>();
  shaderObject->Set(JS_STR("id"), JS_INT(shaderId));

  info.GetReturnValue().Set(shaderObject);
}


NAN_METHOD(WebGLRenderingContext::ShaderSource) {
  GLint shaderId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Nan::Utf8String code(Local<String>::Cast(info[1]));
  GLint length = code.length();

  const char* codes[] = {*code};
  const GLint lengths[] = {length};
  glShaderSource(shaderId, 1, codes, lengths);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::CompileShader) {
  GLint shaderId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  glCompileShader(shaderId);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::FrontFace) {
  GLint arg = TO_INT32(info[0]);
  glFrontFace(arg);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::IsContextLost) {
  info.GetReturnValue().Set(JS_BOOL(false));
}

NAN_GETTER(WebGLRenderingContext::DrawingBufferWidthGetter) {
  // Nan::HandleScope scope;

  Local<Object> glObj = info.This();
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(glObj);

  int width, height;
  windowsystem::GetFramebufferSize(gl->windowHandle, &width, &height);

  info.GetReturnValue().Set(JS_INT(width));
}

NAN_GETTER(WebGLRenderingContext::DrawingBufferHeightGetter) {
  // Nan::HandleScope scope;

  Local<Object> glObj = info.This();
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(glObj);

  int width, height;
  windowsystem::GetFramebufferSize(gl->windowHandle, &width, &height);

  info.GetReturnValue().Set(JS_INT(height));
}

NAN_METHOD(WebGLRenderingContext::GetBoundFramebuffer) {
  Local<Object> glObj = info.This();
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(glObj);

  GLuint target = TO_UINT32(info[0]);
  if (gl->HasFramebufferBinding(target)) {
    Local<Object> fboObject = Nan::New<Object>();
    fboObject->Set(JS_STR("id"), JS_INT(gl->GetFramebufferBinding(target)));
    info.GetReturnValue().Set(fboObject);
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGLRenderingContext::GetDefaultFramebuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  info.GetReturnValue().Set(JS_INT(gl->defaultFramebuffer));
}

NAN_METHOD(WebGLRenderingContext::SetDefaultFramebuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLuint framebuffer = TO_UINT32(info[0]);

  GLuint oldDefaultFramebuffer = gl->defaultFramebuffer;
  GLuint oldFramebuffer = gl->HasFramebufferBinding(GL_FRAMEBUFFER) ? gl->GetFramebufferBinding(GL_FRAMEBUFFER) : 0;
  if (oldFramebuffer == oldDefaultFramebuffer) {
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    gl->SetFramebufferBinding(GL_FRAMEBUFFER, framebuffer);
  }
  GLuint oldReadFramebuffer = gl->HasFramebufferBinding(GL_READ_FRAMEBUFFER) ? gl->GetFramebufferBinding(GL_READ_FRAMEBUFFER) : 0;
  if (oldReadFramebuffer == oldDefaultFramebuffer) {
    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
    gl->SetFramebufferBinding(GL_READ_FRAMEBUFFER, framebuffer);
  }
  GLuint oldDrawFramebuffer = gl->HasFramebufferBinding(GL_DRAW_FRAMEBUFFER) ? gl->GetFramebufferBinding(GL_DRAW_FRAMEBUFFER) : 0;
  if (oldDrawFramebuffer == oldDefaultFramebuffer) {
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
    gl->SetFramebufferBinding(GL_DRAW_FRAMEBUFFER, framebuffer);
  }

  gl->defaultFramebuffer = framebuffer;
}

NAN_METHOD(WebGLRenderingContext::SetClearEnabled) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  bool clearEnabled = TO_BOOL(info[0]);

  gl->clearEnabled = clearEnabled;
}

NAN_METHOD(WebGLRenderingContext::LoadSubTexture) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLuint tex = TO_UINT32(info[0]);
  GLuint x = TO_UINT32(info[1]);
  GLuint y = TO_UINT32(info[2]);
  GLuint width = TO_UINT32(info[3]);
  GLuint height = TO_UINT32(info[4]);
  Local<Uint8Array> bufferUint8Array = Local<Uint8Array>::Cast(info[5]);
  GLuint oldTextureWidth = TO_UINT32(info[6]);
  GLuint oldTextureHeight = TO_UINT32(info[7]);
  GLuint newTextureWidth = TO_UINT32(info[8]);
  GLuint newTextureHeight = TO_UINT32(info[9]);

  windowsystem::SetCurrentWindowContext(gl->windowHandle);

  glBindTexture(GL_TEXTURE_2D, tex);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  if (newTextureWidth != oldTextureWidth || newTextureHeight != oldTextureHeight) {
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
#if !defined(LUMIN) && !defined(ANDROID)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTextureWidth, newTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
#else
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTextureWidth, newTextureHeight, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
#endif
  }

  glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
  glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
  glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
  /* glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
  glPixelStorei(GL_UNPACK_SKIP_ROWS, y); */
  uint8_t *buffer = (uint8_t *)bufferUint8Array->Buffer()->GetContents().Data() + bufferUint8Array->ByteOffset();
#if !defined(LUMIN) && !defined(ANDROID)
  glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
#else
  glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
#endif
  
  // glFlush();
  
  if (gl->HasTextureBinding(gl->activeTexture, GL_TEXTURE_2D)) {
    glBindTexture(GL_TEXTURE_2D, gl->GetTextureBinding(gl->activeTexture, GL_TEXTURE_2D));
  } else {
    glBindTexture(GL_TEXTURE_2D, 0);
  }
  if (gl->HasPixelStoreiBinding(GL_UNPACK_ROW_LENGTH)) {
    glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->GetPixelStoreiBinding(GL_UNPACK_ROW_LENGTH));
  } else {
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
  }
  if (gl->HasPixelStoreiBinding(GL_UNPACK_SKIP_PIXELS)) {
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, gl->GetPixelStoreiBinding(GL_UNPACK_SKIP_PIXELS));
  } else {
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
  }
  if (gl->HasPixelStoreiBinding(GL_UNPACK_SKIP_ROWS)) {
    glPixelStorei(GL_UNPACK_SKIP_ROWS, gl->GetPixelStoreiBinding(GL_UNPACK_SKIP_ROWS));
  } else {
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
  }
}

NAN_METHOD(WebGLRenderingContext::FramebufferTextureMultiviewOVR) {
  GLenum target = TO_UINT32(info[0]);
  GLenum attachment = TO_UINT32(info[1]);
  GLuint texture = info[2]->IsObject() ? TO_UINT32(JS_OBJ(info[2])->Get(JS_STR("id"))) : 0;
  GLint level = TO_INT32(info[3]);
  GLint baseViewIndex = TO_INT32(info[4]);
  GLsizei numViews = TO_UINT32(info[5]);

#if !defined(ANDROID) && !defined(LUMIN)
  glFramebufferTextureMultiviewOVR(target, attachment, texture, level, baseViewIndex, numViews);
#endif
#if defined(ANDROID) || defined(LUMIN)
  glFramebufferTextureMultiviewOVRExt(target, attachment, texture, level, baseViewIndex, numViews);
#endif

}

NAN_METHOD(WebGLRenderingContext::FramebufferTextureMultisampleMultiviewOVR) {
  GLenum target = TO_UINT32(info[0]);
  GLenum attachment = TO_UINT32(info[1]);
  GLuint texture = info[2]->IsObject() ? TO_UINT32(JS_OBJ(info[2])->Get(JS_STR("id"))) : 0;
  GLint level = TO_INT32(info[3]);
  GLsizei samples = TO_UINT32(info[4]);
  GLint baseViewIndex = TO_INT32(info[5]);
  GLsizei numViews = TO_UINT32(info[6]);

#if !defined(ANDROID) && !defined(LUMIN)
  glFramebufferTextureMultisampleMultiviewOVR(target, attachment, texture, level, samples, baseViewIndex, numViews);
#endif
#if defined(ANDROID) || defined(LUMIN)
  glFramebufferTextureMultisampleMultiviewOVRExt(target, attachment, texture, level, samples, baseViewIndex, numViews);
#endif
}

NAN_METHOD(WebGLRenderingContext::GetShaderParameter) {
  GLint shaderId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLint pname = TO_INT32(info[1]);
  int value;
  switch (pname) {
    case GL_DELETE_STATUS:
    case GL_COMPILE_STATUS:
      glGetShaderiv(shaderId, pname, &value);
      info.GetReturnValue().Set(JS_BOOL(static_cast<bool>(value)));
      break;
    case GL_SHADER_TYPE:
      glGetShaderiv(shaderId, pname, &value);
      info.GetReturnValue().Set(JS_FLOAT(static_cast<unsigned long>(value)));
      break;
    case GL_INFO_LOG_LENGTH:
    case GL_SHADER_SOURCE_LENGTH:
      glGetShaderiv(shaderId, pname, &value);
      info.GetReturnValue().Set(JS_FLOAT(static_cast<long>(value)));
      break;
    default:
      Nan::ThrowTypeError("GetShaderParameter: Invalid Enum");
  }

  //info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::GetShaderInfoLog) {
  GLint shaderId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  char Error[1024];
  int Len;

  glGetShaderInfoLog(shaderId, sizeof(Error), &Len, Error);

  info.GetReturnValue().Set(JS_STR(Error, Len));
}


NAN_METHOD(WebGLRenderingContext::CreateProgram) {
  GLuint programId = glCreateProgram();

  Local<Object> programObject = Nan::New<Object>();
  programObject->Set(JS_STR("id"), JS_INT(programId));
  info.GetReturnValue().Set(programObject);
}

NAN_METHOD(WebGLRenderingContext::AttachShader) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLint shaderId = TO_INT32(JS_OBJ(info[1])->Get(JS_STR("id")));

  glAttachShader(programId, shaderId);
}

NAN_METHOD(WebGLRenderingContext::LinkProgram) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  glLinkProgram(programId);
}

NAN_METHOD(WebGLRenderingContext::GetProgramParameter) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  int pname = TO_INT32(info[1]);
  int value;

  switch (pname) {
    case GL_DELETE_STATUS:
    case GL_LINK_STATUS:
    case GL_VALIDATE_STATUS: {
      glGetProgramiv(programId, pname, &value);
      info.GetReturnValue().Set(JS_BOOL(static_cast<bool>(value)));
      break;
    }
    case GL_ATTACHED_SHADERS:
    case GL_ACTIVE_ATTRIBUTES:
    case GL_ACTIVE_UNIFORMS:
    case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
    case GL_TRANSFORM_FEEDBACK_VARYINGS:
    case GL_ACTIVE_UNIFORM_BLOCKS: {
      glGetProgramiv(programId, pname, &value);
      info.GetReturnValue().Set(JS_INT(value));
      break;
    }
    default: {
      Nan::ThrowTypeError("getProgramParameter: Invalid Enum");
      break;
    }
  }
}

NAN_METHOD(WebGLRenderingContext::GetUniformLocation) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Nan::Utf8String name(Local<String>::Cast(info[1]));

  GLint location = glGetUniformLocation(programId, *name);

  if (location != -1) {
    Local<Object> locationObject = Nan::New<Object>();
    locationObject->Set(JS_STR("id"), JS_INT(location));
    info.GetReturnValue().Set(locationObject);
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGLRenderingContext::GetUniformIndices) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Local<Array> uniformNames = Local<Array>::Cast(info[1]);

  std::vector<std::string> uniformNamesV(uniformNames->Length());
  std::vector<GLchar *> uniformNamesV2(uniformNames->Length());
  for (int i = 0; i < uniformNames->Length(); i++) {
    Local<Value> uniformName = uniformNames->Get(i);
    if (uniformName->IsString()) {
      Nan::Utf8String uniformNameUtf8String(Local<String>::Cast(uniformName));
      uniformNamesV[i] = std::string(*uniformNameUtf8String, uniformNameUtf8String.length());
      uniformNamesV2[i] = (GLchar *)uniformNamesV[i].data();
    } else {
      return Nan::ThrowTypeError("getUniformIndices: invalid arguments");
    }
  }
  std::vector<GLuint> uniformIndices(uniformNamesV2.size());

  glGetUniformIndices(programId, uniformNamesV.size(), uniformNamesV2.data(), uniformIndices.data());

  Local<Array> result = Nan::New<Array>(uniformIndices.size());
  for (size_t i = 0; i < uniformIndices.size(); i++) {
    result->Set(i, JS_INT(uniformIndices[i]));
  }
  return info.GetReturnValue().Set(result);
}

NAN_METHOD(WebGLRenderingContext::GetActiveUniforms) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Local<Array> uniformIndices;
  if (info[1]->IsArray()) {
    uniformIndices = Local<Array>::Cast(info[1]);
  } else if (info[1]->IsNumber()) {
    uniformIndices = Nan::New<Array>(1);
    uniformIndices->Set(0, info[1]);
  }
  GLenum pname = TO_UINT32(info[2]);

  std::vector<GLuint> uniformIndicesV(uniformIndices->Length());
  for (int i = 0; i < uniformIndices->Length(); i++) {
    uniformIndicesV[i] = TO_UINT32(uniformIndices->Get(i));
  }
  std::vector<GLint> params(uniformIndicesV.size());

  switch (pname) {
    case GL_UNIFORM_TYPE:
    case GL_UNIFORM_SIZE:
    case GL_UNIFORM_BLOCK_INDEX:
    case GL_UNIFORM_OFFSET:
    case GL_UNIFORM_ARRAY_STRIDE:
    case GL_UNIFORM_MATRIX_STRIDE: {
      glGetActiveUniformsiv(programId, uniformIndicesV.size(), uniformIndicesV.data(), pname, params.data());
      Local<Array> result = Nan::New<Array>(params.size());
      for (size_t i = 0; i < params.size(); i++) {
        result->Set(i, JS_INT(params[i]));
      }
      return info.GetReturnValue().Set(result);
    }
    case GL_UNIFORM_IS_ROW_MAJOR: {
      glGetActiveUniformsiv(programId, uniformIndicesV.size(), uniformIndicesV.data(), pname, params.data());
      Local<Array> result = Nan::New<Array>(params.size());
      for (size_t i = 0; i < params.size(); i++) {
        result->Set(i, JS_INT(params[i]));
      }
      return info.GetReturnValue().Set(result);
    }
    default: {
      return info.GetReturnValue().Set(Nan::Null());
    }
  }
}

NAN_METHOD(WebGLRenderingContext::GetUniformBlockIndex) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Nan::Utf8String uniformBlockName(Local<String>::Cast(info[1]));

  GLint blockIndex = glGetUniformBlockIndex(programId, *uniformBlockName);

  info.GetReturnValue().Set(JS_INT(blockIndex));
}

NAN_METHOD(WebGLRenderingContext::UniformBlockBinding) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint uniformBlockIndex = TO_UINT32(info[1]);
  GLuint uniformBlockBinding = TO_UINT32(info[2]);

  glUniformBlockBinding(programId, uniformBlockIndex, uniformBlockBinding);
}

NAN_METHOD(WebGLRenderingContext::GetActiveUniformBlockName) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint uniformBlockIndex = TO_UINT32(info[1]);

  const GLsizei bufSize = 4096;
  GLsizei length = 0;
  std::vector<GLchar> uniformBlockName(bufSize);

  glGetActiveUniformBlockName(programId, uniformBlockIndex, bufSize, &length, uniformBlockName.data());

  Local<String> result = Nan::New<String>(uniformBlockName.data(), length).ToLocalChecked();
  info.GetReturnValue().Set(result);
}

NAN_METHOD(WebGLRenderingContext::GetActiveUniformBlockParameter) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint uniformBlockIndex = TO_UINT32(info[1]);
  GLenum pname = TO_UINT32(info[2]);

  switch (pname) {
    case GL_UNIFORM_BLOCK_BINDING:
    case GL_UNIFORM_BLOCK_DATA_SIZE:
    case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: {
      GLint param;
      glGetActiveUniformBlockiv(programId, uniformBlockIndex, pname, &param);
      return info.GetReturnValue().Set(JS_INT(param));
    }
    case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
      GLint numUniforms = 0;
      glGetActiveUniformBlockiv(programId, uniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numUniforms);

      Local<ArrayBuffer> activeUniformIndicesArrayBuffer = ArrayBuffer::New(Isolate::GetCurrent(), numUniforms * sizeof(GLuint));
      Local<Uint32Array> activeUniformIndicesUint32Array = Uint32Array::New(activeUniformIndicesArrayBuffer, 0, numUniforms);
      if (numUniforms > 0) {
        glGetActiveUniformBlockiv(programId, uniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, (GLint *)activeUniformIndicesArrayBuffer->GetContents().Data());
      }

      return info.GetReturnValue().Set(activeUniformIndicesUint32Array);
    }
    case GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
    case GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER: {
      GLint param;
      glGetActiveUniformBlockiv(programId, uniformBlockIndex, pname, &param);
      return info.GetReturnValue().Set(JS_BOOL(static_cast<bool>(param)));
    }
    default: {
      return info.GetReturnValue().Set(Nan::Null());
    }
  }
}

NAN_METHOD(WebGLRenderingContext::ClearColor) {
  GLfloat red = TO_FLOAT(info[0]);
  GLfloat green = TO_FLOAT(info[1]);
  GLfloat blue = TO_FLOAT(info[2]);
  GLfloat alpha = TO_FLOAT(info[3]);

  glClearColor(red, green, blue, alpha);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::ClearDepth) {
  GLfloat depth = TO_FLOAT(info[0]);
  glClearDepthf(depth);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::Disable) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLint arg = TO_INT32(info[0]);

  glDisable(arg);
}

NAN_METHOD(WebGLRenderingContext::Enable) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLint arg = TO_INT32(info[0]);

  glEnable(arg);
}


NAN_METHOD(WebGLRenderingContext::CreateTexture) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint texture;
  glGenTextures(1, &texture);

  gl->objectCache.samplers.insert(texture);

  Local<Object> textureObject = Nan::New<Object>();
  textureObject->Set(JS_STR("id"), JS_INT(texture));
  info.GetReturnValue().Set(textureObject);
}


NAN_METHOD(WebGLRenderingContext::BindTexture) {
  Local<Object> glObj = info.This();
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(glObj);

  GLenum target = TO_INT32(info[0]);
  GLuint texture = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : 0;

  glBindTexture(target, texture);

  gl->SetTextureBinding(gl->activeTexture, target, texture);

  // info.GetReturnValue().Set(Nan::Undefined());
}

/* char texPixels[4096 * 4096 * 4];
NAN_METHOD(WebGLRenderingContext::FlipTextureData) {
  Nan::HandleScope scope;

  int num;
  char *pixels=(char*)getArrayData<BYTE>(info[0], &num);
  int width = TO_INT32(info[1]);
  int height = TO_INT32(info[2]);

  int elementSize = num / width / height;
  for (int y = 0; y < height; y++) {
    memcpy(&(texPixels[(height - 1 - y) * width * elementSize]), &pixels[y * width * elementSize], width * elementSize);
  }
  memcpy(pixels, texPixels, num);
} */

NAN_METHOD(WebGLRenderingContext::TexParameteri) {
  int target = TO_INT32(info[0]);
  int pname = TO_INT32(info[1]);
  int param = TO_INT32(info[2]);

  glTexParameteri(target, pname, param);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::TexParameterf) {
  int target = TO_INT32(info[0]);
  int pname = TO_INT32(info[1]);
  float param = TO_FLOAT(info[2]);

  glTexParameterf(target, pname, param);
}


NAN_METHOD(WebGLRenderingContext::Clear) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  if (gl->clearEnabled || (gl->HasFramebufferBinding(GL_DRAW_FRAMEBUFFER) && gl->GetFramebufferBinding(GL_DRAW_FRAMEBUFFER) != gl->defaultFramebuffer)) {
    GLint arg = TO_INT32(info[0]);

    glClear(arg);

    gl->dirty = true;
  }
}


NAN_METHOD(WebGLRenderingContext::UseProgram) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLint programId = info[0]->IsObject() ? TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glUseProgram(programId);

  gl->SetProgramBinding(programId);
}

NAN_METHOD(WebGLRenderingContext::CreateBuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint buffer;
  glGenBuffers(1, &buffer);

  gl->objectCache.buffers.insert(buffer);

  Local<Object> bufferObject = Nan::New<Object>();
  bufferObject->Set(JS_STR("id"), JS_INT(buffer));
  info.GetReturnValue().Set(bufferObject);
}

NAN_METHOD(WebGLRenderingContext::BindBuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLenum target;
  GLuint buffer;
  if (info.Length() < 2) {
    return Nan::ThrowError("BindBuffer requires at least 2 arguments");
  } else if (!info[0]->IsNumber()) {
    return Nan::ThrowError("First argument to BindBuffer must be a number");
  } else if (info[1]->IsObject() && JS_OBJ(info[1])->Get(JS_STR("id"))->IsNumber()) {
    target = TO_UINT32(info[0]);
    buffer = TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id")));
    glBindBuffer(target, buffer);
  } else if (info[1]->IsNull()) {
    target = TO_INT32(info[0]);
    buffer = 0;
  } else {
    return Nan::ThrowError("WebGLRenderingContext::BindBuffer: invalid arguments");
  }

  glBindBuffer(target, buffer);

  gl->SetBufferBinding(target, buffer);
}

NAN_METHOD(WebGLRenderingContext::BindBufferBase) {
  GLenum target = TO_UINT32(info[0]);
  GLuint index = TO_UINT32(info[1]);
  GLuint buffer = info[2]->IsObject() ? TO_UINT32(JS_OBJ(info[2])->Get(JS_STR("id"))) : 0;

  glBindBufferBase(target, index, buffer);
}

NAN_METHOD(WebGLRenderingContext::BindBufferRange) {
  GLenum target = TO_UINT32(info[0]);
  GLuint index = TO_UINT32(info[1]);
  GLuint buffer = info[2]->IsObject() ? TO_UINT32(JS_OBJ(info[2])->Get(JS_STR("id"))) : 0;
  GLintptr offset = TO_UINT32(info[3]);
  GLsizei size = TO_UINT32(info[4]);

  glBindBufferRange(target, index, buffer, offset, size);
}

NAN_METHOD(WebGLRenderingContext::CreateFramebuffer) {
  GLuint framebuffer;
  glGenFramebuffers(1, &framebuffer);

  Local<Object> framebufferObject = Nan::New<Object>();
  framebufferObject->Set(JS_STR("id"), JS_INT(framebuffer));
  info.GetReturnValue().Set(framebufferObject);
}


NAN_METHOD(WebGLRenderingContext::BindFramebuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLenum target = TO_UINT32(info[0]);
  GLuint framebuffer = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : gl->defaultFramebuffer;

  glBindFramebuffer(target, framebuffer);

  gl->SetFramebufferBinding(target, framebuffer);
  if (target == GL_FRAMEBUFFER) {
    gl->SetFramebufferBinding(GL_DRAW_FRAMEBUFFER, framebuffer);
    gl->SetFramebufferBinding(GL_READ_FRAMEBUFFER, framebuffer);
  }
}

NAN_METHOD(WebGLRenderingContext::BindFramebufferRaw) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLenum target = TO_UINT32(info[0]);
  GLuint framebuffer = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : 0;

  glBindFramebuffer(target, framebuffer);
}

NAN_METHOD(WebGLRenderingContext::FramebufferTexture2D) {
  GLenum target = TO_UINT32(info[0]);
  GLenum attachment = TO_INT32(info[1]);
  GLenum textarget = TO_INT32(info[2]);
  GLuint texture = info[3]->IsObject() ? TO_UINT32(JS_OBJ(info[3])->Get(JS_STR("id"))) : 0;
  GLint level = TO_INT32(info[4]);

  glFramebufferTexture2D(target, attachment, textarget, texture, level);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::FramebufferTextureLayer) {
  GLenum target = TO_UINT32(info[0]);
  GLenum attachment = TO_INT32(info[1]);
  GLuint texture = info[2]->IsObject() ? TO_UINT32(JS_OBJ(info[2])->Get(JS_STR("id"))) : 0;
  GLint level = TO_INT32(info[3]);
  GLint layer = TO_INT32(info[4]);

  glFramebufferTextureLayer(target, attachment, texture, level, layer);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::BlitFramebuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  int sx = TO_INT32(info[0]);
  int sy = TO_INT32(info[1]);
  int sw = TO_INT32(info[2]);
  int sh = TO_INT32(info[3]);
  int dx = TO_INT32(info[4]);
  int dy = TO_INT32(info[5]);
  int dw = TO_INT32(info[6]);
  int dh = TO_INT32(info[7]);
  GLbitfield mask = TO_UINT32(info[8]);
  GLenum filter = TO_UINT32(info[9]);

  glBlitFramebuffer(
    sx, sy,
    sw, sh,
    dx, dy,
    dw, dh,
    mask,
    filter
  );

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::InvalidateFramebuffer) {
  GLenum target = TO_UINT32(info[0]);
  Local<Array> attachments = Local<Array>::Cast(info[1]);

  std::vector<GLenum> attachmentsV(attachments->Length());
  for (int i = 0; i < attachments->Length(); i++) {
    attachmentsV[i] = TO_UINT32(attachments->Get(i));
  }

  glInvalidateFramebuffer(target, attachmentsV.size(), attachmentsV.data());
}

NAN_METHOD(WebGLRenderingContext::InvalidateSubFramebuffer) {
  GLenum target = TO_UINT32(info[0]);
  Local<Array> attachments = Local<Array>::Cast(info[1]);
  GLint x = TO_UINT32(info[2]);
  GLint y = TO_UINT32(info[3]);
  GLsizei width = TO_UINT32(info[4]);
  GLsizei height = TO_UINT32(info[5]);

  std::vector<GLenum> attachmentsV(attachments->Length());
  for (int i = 0; i < attachments->Length(); i++) {
    attachmentsV[i] = TO_UINT32(attachments->Get(i));
  }

  glInvalidateSubFramebuffer(target, attachmentsV.size(), attachmentsV.data(), x, y, width, height);
}

NAN_METHOD(WebGLRenderingContext::BufferData) {
  GLenum target = TO_UINT32(info[0]);
  Local<Object> obj = Local<Object>::Cast(info[1]);

  char *data;
  GLuint size;
  GLenum usage;
  if (obj->IsArrayBufferView()) {
    Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(obj);
    data = (char *)arrayBufferView->Buffer()->GetContents().Data() + arrayBufferView->ByteOffset();
    usage = TO_UINT32(info[2]);

    if (info[3]->IsNumber()) {
      size_t srcOffset = TO_UINT32(info[3]) * getArrayBufferViewElementSize(arrayBufferView);
      data += srcOffset;
      size = info[4]->IsNumber() ? TO_UINT32(info[4]) : 0;
      if (size == 0) {
        size = arrayBufferView->ByteLength() - srcOffset;
      }
    } else {
      size = arrayBufferView->ByteLength();
    }
  } else if (obj->IsArrayBuffer()) {
    Local<ArrayBuffer> arrayBuffer = Local<ArrayBuffer>::Cast(obj);
    data = (char *)arrayBuffer->GetContents().Data();
    size = arrayBuffer->ByteLength();
    usage = TO_INT32(info[2]);
  } else if(obj->IsNumber()) {
    data = nullptr;
    size = TO_UINT32(info[1]);
    usage = TO_INT32(info[2]);
  } else if (obj->IsNull() || obj->IsUndefined()) {
    data = nullptr;
    size = 0;
    usage = TO_INT32(info[2]);
  } else {
    Nan::ThrowError("bufferData: invalid arguments");
    return;
  }

  glBufferData(target, size, data, usage);
}


NAN_METHOD(WebGLRenderingContext::BufferSubData) {
  GLenum target = TO_UINT32(info[0]);
  GLint dstOffset = TO_INT32(info[1]);
  Local<Object> obj = Local<Object>::Cast(info[2]);

  char *data;
  GLuint size;
  if (obj->IsArrayBufferView()) {
    Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(obj);
    data = (char *)arrayBufferView->Buffer()->GetContents().Data() + arrayBufferView->ByteOffset();

    if (info[3]->IsNumber()) {
      size_t srcOffset = TO_UINT32(info[3]) * getArrayBufferViewElementSize(arrayBufferView);
      data += srcOffset;
      size = info[4]->IsNumber() ? TO_UINT32(info[4]) : 0;
      if (size == 0) {
        size = arrayBufferView->ByteLength() - srcOffset;
      }
    } else {
      size = arrayBufferView->ByteLength();
    }
  } else if (obj->IsArrayBuffer()) {
    Local<ArrayBuffer> arrayBuffer = Local<ArrayBuffer>::Cast(obj);
    data = (char *)arrayBuffer->GetContents().Data();
    size = arrayBuffer->ByteLength();
  } else {
    Nan::ThrowError("bufferSubData: invalid arguments");
    return;
  }

  glBufferSubData(target, dstOffset, size, data);
}

NAN_METHOD(WebGLRenderingContext::CopyBufferSubData) {
  GLenum readTarget = TO_UINT32(info[0]);
  GLenum writeTarget = TO_UINT32(info[1]);
  GLintptr readOffset = TO_INT32(info[2]);
  GLintptr writeOffset = TO_INT32(info[3]);
  GLsizei size = TO_INT32(info[4]);

  glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
}

NAN_METHOD(WebGLRenderingContext::GetBufferSubData) {
  GLenum target = TO_UINT32(info[0]);
  GLintptr srcByteOffset = TO_INT32(info[1]);
  Local<ArrayBufferView> dstData = Local<ArrayBufferView>::Cast(info[2]);
  char *data = (char *)dstData->Buffer()->GetContents().Data() + dstData->ByteOffset();
  size_t dataLength = dstData->ByteLength();
  if (info[3]->IsNumber()) {
    GLuint dstOffset = TO_UINT32(info[3]);
    data += dstOffset;
    dataLength -= dstOffset;
  }
  if (info[4]->IsNumber()) {
    GLuint length = TO_UINT32(info[4]);
    dataLength = std::min<size_t>(dataLength, length);
  }

#if defined(ANDROID) || defined(LUMIN)
  void *mapping = glMapBufferRange(target, srcByteOffset, dataLength, GL_MAP_READ_BIT);
  if (mapping != nullptr) {
    memcpy(data, mapping, dataLength);
    glUnmapBuffer(target);
  }
#else
  glGetBufferSubData(target, srcByteOffset, dataLength, data);
#endif
}

NAN_METHOD(WebGLRenderingContext::ReadBuffer) {
  GLenum src = TO_UINT32(info[0]);
  glReadBuffer(src);
}

NAN_METHOD(WebGLRenderingContext::BlendEquation) {
  GLint mode = TO_INT32(info[0]);
  glBlendEquation(mode);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::BlendFunc) {
  GLint sfactor = TO_INT32(info[0]);
  GLint dfactor = TO_INT32(info[1]);

  glBlendFunc(sfactor, dfactor);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::EnableVertexAttribArray) {
  GLint arg = TO_INT32(info[0]);
  glEnableVertexAttribArray(arg);

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::VertexAttribPointer) {
  GLuint indx = TO_UINT32(info[0]);
  GLint size = TO_INT32(info[1]);
  GLenum type = TO_UINT32(info[2]);
  GLboolean normalized = TO_BOOL(info[3]);
  GLint stride = TO_INT32(info[4]);
  GLint offset = TO_INT32(info[5]);

  //    printf("VertexAttribPointer %d %d %d %d %d %d\n", indx, size, type, normalized, stride, offset);
  glVertexAttribPointer(indx, size, type, normalized, stride, (const GLvoid *)offset);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::VertexAttribIPointer) {
  GLuint indx = TO_UINT32(info[0]);
  GLint size = TO_INT32(info[1]);
  GLenum type = TO_UINT32(info[2]);
  GLint stride = TO_INT32(info[3]);
  GLint offset = TO_INT32(info[4]);

  glVertexAttribIPointer(indx, size, type, stride, (const GLvoid *)offset);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::ActiveTexture) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum activeTexture = TO_UINT32(info[0]);

  glActiveTexture(activeTexture);

  gl->activeTexture = activeTexture;

  // info.GetReturnValue().Set(Nan::Undefined());
}


NAN_METHOD(WebGLRenderingContext::DrawElements) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum mode = TO_UINT32(info[0]);
  GLsizei count = TO_INT32(info[1]);
  GLenum type = TO_UINT32(info[2]);
  GLvoid *offset = reinterpret_cast<GLvoid*>(TO_UINT32(info[3]));

  glDrawElements(mode, count, type, offset);

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DrawElementsInstanced) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum mode = TO_UINT32(info[0]);
  GLsizei count = TO_INT32(info[1]);
  GLenum type = TO_UINT32(info[2]);
  GLvoid *offset = reinterpret_cast<GLvoid*>(TO_UINT32(info[3]));
  GLsizei primcount = TO_INT32(info[4]);

  glDrawElementsInstanced(mode, count, type, offset, primcount);

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DrawElementsInstancedANGLE) {
  Local<Object> contextObj = Local<Object>::Cast(info.This()->Get(JS_STR("context")));
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(contextObj);
  GLenum mode = TO_UINT32(info[0]);
  GLsizei count = TO_INT32(info[1]);
  GLenum type = TO_UINT32(info[2]);
  GLvoid *offset = reinterpret_cast<GLvoid*>(TO_UINT32(info[3]));
  GLsizei primcount = TO_INT32(info[4]);

  glDrawElementsInstanced(mode, count, type, offset, primcount);

  gl->dirty = true;

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DrawRangeElements) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum mode = TO_UINT32(info[0]);
  GLuint start = TO_UINT32(info[1]);
  GLuint end = TO_UINT32(info[2]);
  GLsizei count = TO_UINT32(info[3]);
  GLenum type = TO_UINT32(info[4]);
  GLintptr offset = TO_INT32(info[5]);

  glDrawRangeElements(mode, start, end, count, type, (void *)offset);

  gl->dirty = true;
}

NAN_METHOD(WebGLRenderingContext::Flush) {
  // Nan::HandleScope scope;

  glFlush();

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::Finish) {
  // Nan::HandleScope scope;

  glFinish();

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib1f) {
  GLuint indx = TO_UINT32(info[0]);
  GLfloat x = TO_FLOAT(info[1]);

  glVertexAttrib1f(indx, x);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib2f) {
  GLuint indx = TO_UINT32(info[0]);
  float x = TO_FLOAT(info[1]);
  float y = TO_FLOAT(info[2]);

  glVertexAttrib2f(indx, x, y);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib3f) {
  GLuint indx = TO_INT32(info[0]);
  float x = TO_FLOAT(info[1]);
  float y = TO_FLOAT(info[2]);
  float z = TO_FLOAT(info[3]);

  glVertexAttrib3f(indx, x, y, z);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib4f) {
  GLuint indx = TO_UINT32(info[0]);
  float x = TO_FLOAT(info[1]);
  float y = TO_FLOAT(info[2]);
  float z = TO_FLOAT(info[3]);
  float w = TO_FLOAT(info[4]);

  glVertexAttrib4f(indx, x, y, z, w);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib1fv) {
  GLuint indx = TO_UINT32(info[0]);

  GLfloat *data;
  int num;
  if (info[1]->IsArray()) {
    Local<Array> array = Local<Array>::Cast(info[1]);
    unsigned int length = array->Length();
    Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
    for (unsigned int i = 0; i < length; i++) {
      float32Array->Set(i, array->Get(i));
    }
    data = getArrayData<GLfloat>(float32Array, &num);
  } else {
    data = getArrayData<GLfloat>(info[1], &num);
  }

  glVertexAttrib1fv(indx, data);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib2fv) {
  int indx = TO_INT32(info[0]);

  GLfloat *data;
  int num;
  if (info[1]->IsArray()) {
    Local<Array> array = Local<Array>::Cast(info[1]);
    unsigned int length = array->Length();
    Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
    for (unsigned int i = 0; i < length; i++) {
      float32Array->Set(i, array->Get(i));
    }
    data=getArrayData<GLfloat>(float32Array, &num);
  } else {
    data=getArrayData<GLfloat>(info[1], &num);
  }

  glVertexAttrib2fv(indx, data);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib3fv) {
  int indx = TO_INT32(info[0]);

  GLfloat *data;
  int num;
  if (info[1]->IsArray()) {
    Local<Array> array = Local<Array>::Cast(info[1]);
    unsigned int length = array->Length();
    Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
    for (unsigned int i = 0; i < length; i++) {
      float32Array->Set(i, array->Get(i));
    }
    data = getArrayData<GLfloat>(float32Array, &num);
  } else {
    data = getArrayData<GLfloat>(info[1], &num);
  }

  glVertexAttrib3fv(indx, data);
}

NAN_METHOD(WebGLRenderingContext::VertexAttrib4fv) {
  int indx = TO_INT32(info[0]);

  GLfloat *data;
  int num;
  if (info[1]->IsArray()) {
    Local<Array> array = Local<Array>::Cast(info[1]);
    unsigned int length = array->Length();
    Local<Float32Array> float32Array = Float32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
    for (unsigned int i = 0; i < length; i++) {
      float32Array->Set(i, array->Get(i));
    }
    data=getArrayData<GLfloat>(float32Array, &num);
  } else {
    data=getArrayData<GLfloat>(info[1], &num);
  }

  glVertexAttrib4fv(indx, data);
}

NAN_METHOD(WebGLRenderingContext::VertexAttribI4i) {
  GLint index = TO_INT32(info[0]);
  GLint v0 = TO_INT32(info[1]);
  GLint v1 = TO_INT32(info[2]);
  GLint v2 = TO_INT32(info[3]);
  GLint v3 = TO_INT32(info[4]);

  glVertexAttribI4i(index, v0, v1, v2, v3);
}

NAN_METHOD(WebGLRenderingContext::VertexAttribI4iv) {
  GLuint index = TO_UINT32(info[0]);
  Local<Value> dataValue = info[1];

  GLint *data;
  GLsizei count;
  if (dataValue->IsArray()) {
    Local<Array> array = Local<Array>::Cast(dataValue);
    unsigned int length = array->Length();
    Local<Int32Array> int32Array = Int32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
    for (unsigned int i = 0; i < length; i++) {
      int32Array->Set(i, array->Get(i));
    }
    data = getArrayData<GLint>(int32Array, &count);
  } else {
    data = getArrayData<GLint>(dataValue, &count);
  }

  glVertexAttribI4iv(index, data);
}

NAN_METHOD(WebGLRenderingContext::VertexAttribI4ui) {
  GLuint index = TO_UINT32(info[0]);
  GLuint v0 = TO_UINT32(info[1]);
  GLuint v1 = TO_UINT32(info[2]);
  GLuint v2 = TO_UINT32(info[3]);
  GLuint v3 = TO_UINT32(info[4]);

  glVertexAttribI4ui(index, v0, v1, v2, v3);
}

NAN_METHOD(WebGLRenderingContext::VertexAttribI4uiv) {
  GLuint index = TO_UINT32(info[0]);
  Local<Value> dataValue = info[1];

  GLuint *data;
  GLsizei count;
  if (dataValue->IsArray()) {
    Local<Array> array = Local<Array>::Cast(dataValue);
    unsigned int length = array->Length();
    Local<Uint32Array> uint32Array = Uint32Array::New(ArrayBuffer::New(Isolate::GetCurrent(), length * 4), 0, length);
    for (unsigned int i = 0; i < length; i++) {
      uint32Array->Set(i, array->Get(i));
    }
    data = getArrayData<GLuint>(uint32Array, &count);
  } else {
    data = getArrayData<GLuint>(dataValue, &count);
  }

  glVertexAttribI4uiv(index, data);
}

NAN_METHOD(WebGLRenderingContext::VertexAttribDivisor) {
  GLuint index = TO_UINT32(info[0]);
  GLuint divisor = TO_UINT32(info[1]);

  glVertexAttribDivisor(index, divisor);
}

NAN_METHOD(WebGLRenderingContext::VertexAttribDivisorANGLE) {
  GLuint index = TO_UINT32(info[0]);
  GLuint divisor = TO_UINT32(info[1]);

  glVertexAttribDivisor(index, divisor);
}

NAN_METHOD(WebGLRenderingContext::DrawBuffers) {
  Local<Array> buffersArray = Local<Array>::Cast(info[0]);
  GLenum buffers[32];
  size_t numBuffers = std::min<size_t>(buffersArray->Length(), sizeof(buffers)/sizeof(buffers[0]));
  for (size_t i = 0; i < numBuffers; i++) {
    buffers[i] = TO_UINT32(buffersArray->Get(i));
  }

  glDrawBuffers(numBuffers, buffers);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DrawBuffersWEBGL) {
  Local<Array> buffersArray = Local<Array>::Cast(info[0]);
  GLenum buffers[32];
  size_t numBuffers = std::min<size_t>(buffersArray->Length(), sizeof(buffers)/sizeof(buffers[0]));
  for (size_t i = 0; i < numBuffers; i++) {
    buffers[i] = TO_UINT32(buffersArray->Get(i));
  }

  glDrawBuffers(numBuffers, buffers);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::ClearBufferfv) {
  GLenum buffer = TO_UINT32(info[0]);
  GLint drawBuffer = TO_INT32(info[1]);
  Local<Value> valuesValue = info[3];
  GLint srcOffset = info[4]->IsNumber() ? TO_INT32(info[1]) : 0;

  if (valuesValue->IsArray()) {
    Local<Array> valuesArray = Local<Array>::Cast(valuesValue);
    size_t length = std::max<size_t>(valuesArray->Length() - srcOffset, 0);
    if (length > 0) {
      std::vector<GLfloat> values(length);
      for (size_t i = 0; i < length; i++) {
        values[i] = TO_FLOAT(valuesArray->Get(i + srcOffset));
      }
      glClearBufferfv(buffer, drawBuffer, values.data());
    }
  } else if (valuesValue->IsFloat32Array()) {
    Local<Float32Array> valuesFloat32Array = Local<Float32Array>::Cast(valuesValue);
    GLfloat *values = (GLfloat *)((char *)valuesFloat32Array->Buffer()->GetContents().Data() + valuesFloat32Array->ByteOffset());
    glClearBufferfv(buffer, drawBuffer, values);
  } else {
    Nan::ThrowError("ClearBufferfv: Invalid arguments");
  }
}

NAN_METHOD(WebGLRenderingContext::ClearBufferiv) {
  GLenum buffer = TO_UINT32(info[0]);
  GLint drawBuffer = TO_INT32(info[1]);
  Local<Value> valuesValue = info[3];
  GLint srcOffset = info[4]->IsNumber() ? TO_INT32(info[1]) : 0;

  if (valuesValue->IsArray()) {
    Local<Array> valuesArray = Local<Array>::Cast(valuesValue);
    size_t length = std::max<size_t>(valuesArray->Length() - srcOffset, 0);
    if (length > 0) {
      std::vector<GLint> values(length);
      for (size_t i = 0; i < length; i++) {
        values[i] = TO_INT32(valuesArray->Get(i + srcOffset));
      }
      glClearBufferiv(buffer, drawBuffer, values.data());
    }
  } else if (valuesValue->IsInt32Array()) {
    Local<Int32Array> valuesInt32Array = Local<Int32Array>::Cast(valuesValue);
    GLint *values = (GLint *)((char *)valuesInt32Array->Buffer()->GetContents().Data() + valuesInt32Array->ByteOffset());
    glClearBufferiv(buffer, drawBuffer, values);
  } else {
    Nan::ThrowError("ClearBufferiv: Invalid arguments");
  }
}

NAN_METHOD(WebGLRenderingContext::ClearBufferuiv) {
  GLenum buffer = TO_UINT32(info[0]);
  GLint drawBuffer = TO_INT32(info[1]);
  Local<Value> valuesValue = info[3];
  GLint srcOffset = info[4]->IsNumber() ? TO_INT32(info[1]) : 0;

  if (valuesValue->IsArray()) {
    Local<Array> valuesArray = Local<Array>::Cast(valuesValue);
    size_t length = std::max<size_t>(valuesArray->Length() - srcOffset, 0);
    if (length > 0) {
      std::vector<GLuint> values(length);
      for (size_t i = 0; i < length; i++) {
        values[i] = TO_UINT32(valuesArray->Get(i + srcOffset));
      }
      glClearBufferuiv(buffer, drawBuffer, values.data());
    }
  } else if (valuesValue->IsUint32Array()) {
    Local<Uint32Array> valuesUint32Array = Local<Uint32Array>::Cast(valuesValue);
    GLuint *values = (GLuint *)((char *)valuesUint32Array->Buffer()->GetContents().Data() + valuesUint32Array->ByteOffset());
    glClearBufferuiv(buffer, drawBuffer, values);
  } else {
    Nan::ThrowError("ClearBufferiv: Invalid arguments");
  }
}

NAN_METHOD(WebGLRenderingContext::ClearBufferfi) {
  GLenum buffer = TO_UINT32(info[0]);
  GLint drawBuffer = TO_INT32(info[1]);
  GLfloat depth = TO_FLOAT(info[2]);
  GLint stencil = TO_INT32(info[3]);

  glClearBufferfi(buffer, drawBuffer, depth, stencil);
}

NAN_METHOD(WebGLRenderingContext::BlendColor) {
  GLclampf r = TO_FLOAT(info[0]);
  GLclampf g = TO_FLOAT(info[1]);
  GLclampf b = TO_FLOAT(info[2]);
  GLclampf a = TO_FLOAT(info[3]);

  glBlendColor(r, g, b, a);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::BlendEquationSeparate) {
  GLenum modeRGB = TO_INT32(info[0]);
  GLenum modeAlpha = TO_INT32(info[1]);

  glBlendEquationSeparate(modeRGB, modeAlpha);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::BlendFuncSeparate) {
  GLenum srcRGB = TO_INT32(info[0]);
  GLenum dstRGB = TO_INT32(info[1]);
  GLenum srcAlpha = TO_INT32(info[2]);
  GLenum dstAlpha = TO_INT32(info[3]);

  glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::ClearStencil) {
  GLint s = TO_INT32(info[0]);

  glClearStencil(s);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::ColorMask) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLboolean r = TO_BOOL(info[0]);
  GLboolean g = TO_BOOL(info[1]);
  GLboolean b = TO_BOOL(info[2]);
  GLboolean a = TO_BOOL(info[3]);

  glColorMask(r, g, b, a);

  gl->colorMaskState = ColorMaskState(r, g, b, a);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::CopyTexImage2D) {
  GLenum target = TO_UINT32(info[0]);
  GLint level = TO_INT32(info[1]);
  GLenum internalformat = TO_UINT32(info[2]);
  GLint x = TO_INT32(info[3]);
  GLint y = TO_INT32(info[4]);
  GLsizei width = TO_UINT32(info[5]);
  GLsizei height = TO_UINT32(info[6]);
  GLint border = TO_INT32(info[7]);

  glCopyTexImage2D(target, level, internalformat, x, y, width, height, border);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::CopyTexSubImage2D) {
  GLenum target = TO_UINT32(info[0]);
  GLint level = TO_INT32(info[1]);
  GLint xoffset = TO_INT32(info[2]);
  GLint yoffset = TO_INT32(info[3]);
  GLint x = TO_INT32(info[4]);
  GLint y = TO_INT32(info[5]);
  GLsizei width = TO_UINT32(info[6]);
  GLsizei height = TO_UINT32(info[7]);

  glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::CullFace) {
  GLenum mode = TO_INT32(info[0]);

  glCullFace(mode);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DepthMask) {
  GLboolean flag = TO_BOOL(info[0]);

  glDepthMask(flag);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DepthRange) {
  GLclampf zNear = TO_FLOAT(info[0]);
  GLclampf zFar = TO_FLOAT(info[1]);

  glDepthRangef(zNear, zFar);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DisableVertexAttribArray) {
  GLuint index = TO_INT32(info[0]);
  glDisableVertexAttribArray(index);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::Hint) {
  GLenum target = TO_INT32(info[0]);
  GLenum mode = TO_INT32(info[1]);

  glHint(target, mode);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::IsEnabled) {
  GLenum cap = TO_UINT32(info[0]);
  bool ret = glIsEnabled(cap);

  info.GetReturnValue().Set(Nan::New<Boolean>(ret));
}

NAN_METHOD(WebGLRenderingContext::LineWidth) {
  GLfloat width = TO_FLOAT(info[0]);
  glLineWidth(width);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::PolygonOffset) {
  GLfloat factor = TO_FLOAT(info[0]);
  GLfloat units = TO_FLOAT(info[1]);

  glPolygonOffset(factor, units);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::SampleCoverage) {
  GLclampf value = TO_FLOAT(info[0]);
  GLboolean invert = TO_BOOL(info[1]);

  glSampleCoverage(value, invert);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::Scissor) {
  GLint x = TO_INT32(info[0]);
  GLint y = TO_INT32(info[1]);
  GLsizei width = TO_UINT32(info[2]);
  GLsizei height = TO_UINT32(info[3]);

  glScissor(x, y, width, height);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::StencilFunc) {
  GLenum func = TO_INT32(info[0]);
  GLint ref = TO_INT32(info[1]);
  GLuint mask = TO_INT32(info[2]);

  glStencilFunc(func, ref, mask);
}

NAN_METHOD(WebGLRenderingContext::StencilFuncSeparate) {
  GLenum face = TO_INT32(info[0]);
  GLenum func = TO_INT32(info[1]);
  GLint ref = TO_INT32(info[2]);
  GLuint mask = TO_INT32(info[3]);

  glStencilFuncSeparate(face, func, ref, mask);
}

NAN_METHOD(WebGLRenderingContext::StencilMask) {
  GLuint mask = TO_UINT32(info[0]);

  glStencilMask(mask);
}

NAN_METHOD(WebGLRenderingContext::StencilMaskSeparate) {
  GLenum face = TO_INT32(info[0]);
  GLuint mask = TO_UINT32(info[1]);

  glStencilMaskSeparate(face, mask);
}

NAN_METHOD(WebGLRenderingContext::StencilOp) {
  GLenum fail = TO_INT32(info[0]);
  GLenum zfail = TO_INT32(info[1]);
  GLenum zpass = TO_INT32(info[2]);

  glStencilOp(fail, zfail, zpass);
}

NAN_METHOD(WebGLRenderingContext::StencilOpSeparate) {
  GLenum face = TO_INT32(info[0]);
  GLenum fail = TO_INT32(info[1]);
  GLenum zfail = TO_INT32(info[2]);
  GLenum zpass = TO_INT32(info[3]);

  glStencilOpSeparate(face, fail, zfail, zpass);
}

NAN_METHOD(WebGLRenderingContext::BindRenderbuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLenum target = TO_INT32(info[0]);
  GLuint renderbuffer = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : 0;

  glBindRenderbuffer(target, renderbuffer);

  gl->SetRenderbufferBinding(target, renderbuffer);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::CreateRenderbuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint renderbuffer;
  glGenRenderbuffers(1, &renderbuffer);

  gl->objectCache.renderbuffers.insert(renderbuffer);

  Local<Object> renderbufferObject = Nan::New<Object>();
  renderbufferObject->Set(JS_STR("id"), JS_INT(renderbuffer));
  info.GetReturnValue().Set(renderbufferObject);
}

NAN_METHOD(WebGLRenderingContext::DeleteBuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint buffer = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glDeleteBuffers(1, &buffer);

  gl->objectCache.buffers.erase(buffer);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DeleteFramebuffer) {
  GLuint framebuffer = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glDeleteFramebuffers(1, &framebuffer);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DeleteProgram) {
  GLint programId = info[0]->IsObject() ? TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glDeleteProgram(programId);
}

NAN_METHOD(WebGLRenderingContext::DeleteRenderbuffer) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint renderbuffer = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  gl->objectCache.renderbuffers.erase(renderbuffer);

  glDeleteRenderbuffers(1, &renderbuffer);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DeleteShader) {
  GLuint shaderId = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glDeleteShader(shaderId);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DeleteTexture) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint texture = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  gl->objectCache.samplers.erase(texture);

  glDeleteTextures(1, &texture);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::DetachShader) {
  GLuint programId = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint shaderId = TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id")));

  glDetachShader(programId, shaderId);
}

NAN_METHOD(WebGLRenderingContext::FramebufferRenderbuffer) {
  GLenum target = TO_INT32(info[0]);
  GLenum attachment = TO_INT32(info[1]);
  GLenum renderbuffertarget = TO_INT32(info[2]);
  GLuint renderbuffer = info[3]->IsObject() ? TO_UINT32(JS_OBJ(info[3])->Get(JS_STR("id"))) : 0;

  glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::GetVertexAttribOffset) {
  GLuint index = TO_UINT32(info[0]);
  GLenum pname = TO_UINT32(info[1]);
  void *ret = nullptr;

  glGetVertexAttribPointerv(index, pname, &ret);

  info.GetReturnValue().Set(JS_INT(toGLuint(ret)));
}

NAN_METHOD(WebGLRenderingContext::GetShaderPrecisionFormat) {
  GLenum shaderType = TO_INT32(info[0]);
  GLenum precisionType = TO_INT32(info[1]);
  GLint range[2];
  GLint precision;

  glGetShaderPrecisionFormat(shaderType, precisionType, range, &precision);

  Local<Object> result = Object::New(Isolate::GetCurrent());
  result->Set(JS_STR("rangeMin"), JS_INT(range[0]));
  result->Set(JS_STR("rangeMax"), JS_INT(range[1]));
  result->Set(JS_STR("precision"), JS_INT(precision));
  info.GetReturnValue().Set(result);
}

NAN_METHOD(WebGLRenderingContext::IsBuffer) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsBuffer(arg);

    info.GetReturnValue().Set(Nan::New<Boolean>(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsFramebuffer) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsFramebuffer(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsProgram) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsProgram(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsRenderbuffer) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsRenderbuffer(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsShader) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsShader(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsTexture) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsTexture(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsVertexArray) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsVertexArray(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::IsSync) {
  if (info[0]->IsObject()) {
    Local<Value> syncId = JS_OBJ(info[0])->Get(JS_STR("id"));
    if (syncId->IsArray()) {
      Local<Array> syncArray = Local<Array>::Cast(syncId);
      if (syncArray->Get(0)->IsNumber() && syncArray->Get(1)->IsNumber()) {
        GLsync sync = (GLsync)arrayToPointer(syncArray);
        bool ret = glIsSync(sync);

        info.GetReturnValue().Set(JS_BOOL(ret));
      } else {
        info.GetReturnValue().Set(Nan::New<Boolean>(false));
      }
    } else {
      info.GetReturnValue().Set(Nan::New<Boolean>(false));
    }
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGLRenderingContext::RenderbufferStorage) {
  GLenum target = TO_INT32(info[0]);
  GLenum internalformat = TO_INT32(info[1]);
  GLsizei width = TO_UINT32(info[2]);
  GLsizei height = TO_UINT32(info[3]);

  glRenderbufferStorage(target, internalformat, width, height);
}

NAN_METHOD(WebGLRenderingContext::RenderbufferStorageMultisample) {
  GLenum target = TO_UINT32(info[0]);
  GLsizei samples = TO_UINT32(info[1]);
  GLenum internalformat = TO_UINT32(info[2]);
  GLsizei width = TO_UINT32(info[3]);
  GLsizei height = TO_UINT32(info[4]);

  glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
}

NAN_METHOD(WebGLRenderingContext::GetShaderSource) {
  GLuint shaderId = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

  GLint len;
  glGetShaderiv(shaderId, GL_SHADER_SOURCE_LENGTH, &len);
  GLchar *source = new GLchar[len];

  glGetShaderSource(shaderId, len, nullptr, source);

  Local<String> str = JS_STR(source, len);
  delete[] source;

  info.GetReturnValue().Set(str);
}

NAN_METHOD(WebGLRenderingContext::ValidateProgram) {
  GLuint programId = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

  glValidateProgram(programId);
}

NAN_METHOD(WebGLRenderingContext::TexStorage2D) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum target = TO_UINT32(info[0]);
  GLint levels = TO_INT32(info[1]);
  GLenum internalFormat = TO_UINT32(info[2]);
  GLsizei width = TO_UINT32(info[3]);
  GLsizei height = TO_UINT32(info[4]);

  glTexStorage2D(target, levels, internalFormat, width, height);
}

NAN_METHOD(WebGLRenderingContext::TexStorage3D) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLenum target = TO_UINT32(info[0]);
  GLint levels = TO_INT32(info[1]);
  GLenum internalFormat = TO_UINT32(info[2]);
  GLsizei width = TO_UINT32(info[3]);
  GLsizei height = TO_UINT32(info[4]);
  GLsizei depth = TO_UINT32(info[5]);

  glTexStorage3D(target, levels, internalFormat, width, height, depth);
}

NAN_METHOD(WebGLRenderingContext::ReadPixels) {
  GLint x = TO_INT32(info[0]);
  GLint y = TO_INT32(info[1]);
  GLsizei width = TO_UINT32(info[2]);
  GLsizei height = TO_UINT32(info[3]);
  GLenum format = TO_UINT32(info[4]);
  GLenum type = TO_UINT32(info[5]);

  char *pixels;
  if (info[6]->IsNumber()) {
    GLintptr offset = TO_UINT32(info[6]);
    pixels = (char *)offset;
  } else if (info[6]->IsArrayBufferView()) {
    Local<ArrayBufferView> arrayBufferView = Local<ArrayBufferView>::Cast(info[6]);
    pixels = (char *)getImageData(arrayBufferView);

    if (pixels != nullptr) {
      if (info[7]->IsNumber()) {
        GLuint dstOffset = TO_UINT32(info[7]);
        GLuint elementSize = getArrayBufferViewElementSize(arrayBufferView);
        pixels += dstOffset * elementSize;
      }
    } else {
      return Nan::ThrowError("ReadPixels: Invalid data argument");
    }
  } else {
    return Nan::ThrowError("ReadPixels: Invalid arguments");
  }

  glReadPixels(x, y, width, height, format, type, pixels);
}

NAN_METHOD(WebGLRenderingContext::GetTexParameter) {
  GLenum target = TO_INT32(info[0]);
  GLenum pname = TO_INT32(info[1]);
  GLint value;

  glGetTexParameteriv(target, pname, &value);

  info.GetReturnValue().Set(JS_INT(value));
}

NAN_METHOD(WebGLRenderingContext::GetActiveAttrib) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint index = TO_INT32(info[1]);

  char name[1024];
  GLsizei length = 0;
  GLsizei size;
  GLenum type;

  glGetActiveAttrib(programId, index, sizeof(name), &length, &size, &type, name);

  if (length > 0) {
    Local<Object> activeInfo = Nan::New<Object>();
    activeInfo->Set(JS_STR("size"), JS_INT(size));
    activeInfo->Set(JS_STR("type"), JS_INT((int)type));
    activeInfo->Set(JS_STR("name"), JS_STR(name, length));

    info.GetReturnValue().Set(activeInfo);
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGLRenderingContext::GetActiveUniform) {
  GLint programId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint index = TO_INT32(info[1]);

  char name[1024];
  GLsizei length = 0;
  GLsizei size;
  GLenum type;

  glGetActiveUniform(programId, index, sizeof(name), &length, &size, &type, name);

  if (length > 0) {
    Local<Object> activeInfo = Nan::New<Object>();
    activeInfo->Set(JS_STR("size"), JS_INT(size));
    activeInfo->Set(JS_STR("type"), JS_INT((int)type));
    activeInfo->Set(JS_STR("name"), JS_STR(name, length));

    info.GetReturnValue().Set(activeInfo);
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGLRenderingContext::GetAttachedShaders) {
  GLuint programId = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint shaders[1024];
  GLsizei count;

  glGetAttachedShaders(programId, sizeof(shaders)/sizeof(shaders[0]), &count, shaders);

  Local<Array> shadersArr = Nan::New<Array>(count);
  for(int i = 0; i < count; i++) {
    Local<Object> shaderObject = Nan::New<Object>();
    shaderObject->Set(JS_STR("id"), JS_INT(shaders[i]));
    shadersArr->Set(i, shaderObject);
  }

  info.GetReturnValue().Set(shadersArr);
}

NAN_METHOD(WebGLRenderingContext::GetParameter) {
  GLenum name = TO_INT32(info[0]);

  switch (name) {
    case GL_RED_BITS:
    case GL_GREEN_BITS:
    case GL_BLUE_BITS:
    case GL_ALPHA_BITS:
    case GL_STENCIL_BITS: {
      info.GetReturnValue().Set(JS_INT(8));
      break;
    }
    case GL_DEPTH_BITS: {
      info.GetReturnValue().Set(JS_INT(24));
      break;
    }
    case GL_BLEND:
    case GL_CULL_FACE:
    case GL_DEPTH_TEST:
    case GL_DEPTH_WRITEMASK:
    case GL_DITHER:
    case GL_POLYGON_OFFSET_FILL:
    case GL_SAMPLE_COVERAGE_INVERT:
    case GL_SCISSOR_TEST:
    case GL_STENCIL_TEST:
    {
      // return a boolean
      GLboolean params;
      glGetBooleanv(name, &params);
      info.GetReturnValue().Set(JS_BOOL(static_cast<bool>(params)));
      break;
    }
    case GL_BLEND_DST_ALPHA:
    case GL_BLEND_DST_RGB:
    case GL_BLEND_EQUATION:
    case GL_BLEND_EQUATION_ALPHA:
    // case GL_BLEND_EQUATION_RGB: // === GL_BLEND_EQUATION
    case GL_BLEND_SRC_ALPHA:
    case GL_BLEND_SRC_RGB:
    case GL_CULL_FACE_MODE:
    case GL_DEPTH_FUNC:
    case GL_FRONT_FACE:
    case GL_GENERATE_MIPMAP_HINT:
    case GL_IMPLEMENTATION_COLOR_READ_FORMAT:
    case GL_IMPLEMENTATION_COLOR_READ_TYPE:
    case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
    case GL_MAX_CUBE_MAP_TEXTURE_SIZE:
    case GL_MAX_FRAGMENT_UNIFORM_VECTORS:
    case GL_MAX_RENDERBUFFER_SIZE:
    case GL_MAX_TEXTURE_IMAGE_UNITS:
    case GL_MAX_TEXTURE_SIZE:
    case GL_MAX_VARYING_VECTORS:
    case GL_MAX_VERTEX_ATTRIBS:
    case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
    case GL_MAX_VERTEX_UNIFORM_VECTORS:
    case GL_PACK_ALIGNMENT:
    case GL_SAMPLE_BUFFERS:
    case GL_SAMPLES:
    case GL_STENCIL_BACK_FAIL:
    case GL_STENCIL_BACK_FUNC:
    case GL_STENCIL_BACK_PASS_DEPTH_FAIL:
    case GL_STENCIL_BACK_PASS_DEPTH_PASS:
    case GL_STENCIL_BACK_REF:
    case GL_STENCIL_BACK_VALUE_MASK:
    case GL_STENCIL_BACK_WRITEMASK:
    case GL_STENCIL_CLEAR_VALUE:
    case GL_STENCIL_FAIL:
    case GL_STENCIL_FUNC:
    case GL_STENCIL_PASS_DEPTH_FAIL:
    case GL_STENCIL_PASS_DEPTH_PASS:
    case GL_STENCIL_REF:
    case GL_STENCIL_VALUE_MASK:
    case GL_STENCIL_WRITEMASK:
    case GL_SUBPIXEL_BITS:
    case GL_UNPACK_ALIGNMENT:
    case UNPACK_COLORSPACE_CONVERSION_WEBGL:
    case GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
    case GL_MAX_COLOR_ATTACHMENTS:
    case GL_MAX_DRAW_BUFFERS:
    case GL_DRAW_BUFFER0:
    case GL_DRAW_BUFFER1:
    case GL_DRAW_BUFFER2:
    case GL_DRAW_BUFFER3:
    case GL_DRAW_BUFFER4:
    case GL_DRAW_BUFFER5:
    case GL_DRAW_BUFFER6:
    case GL_DRAW_BUFFER7:
    case GL_DRAW_BUFFER8:
    case GL_DRAW_BUFFER9:
    case GL_DRAW_BUFFER10:
    case GL_DRAW_BUFFER11:
    case GL_DRAW_BUFFER12:
    case GL_DRAW_BUFFER13:
    case GL_DRAW_BUFFER14:
    case GL_DRAW_BUFFER15:
    case GL_MAX_3D_TEXTURE_SIZE:
    case GL_MAX_ARRAY_TEXTURE_LAYERS:
    case GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS:
    case GL_MAX_COMBINED_UNIFORM_BLOCKS:
    case GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS:
    case GL_MAX_ELEMENTS_INDICES:
    case GL_MAX_ELEMENTS_VERTICES:
    case GL_MAX_FRAGMENT_INPUT_COMPONENTS:
    case GL_MAX_FRAGMENT_UNIFORM_BLOCKS:
    case GL_MAX_FRAGMENT_UNIFORM_COMPONENTS:
    case GL_MAX_PROGRAM_TEXEL_OFFSET:
    case GL_MAX_SAMPLES:
    case GL_MAX_TEXTURE_LOD_BIAS:
    case GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS:
    case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
    case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS:
    case GL_MAX_UNIFORM_BLOCK_SIZE:
    case GL_MAX_UNIFORM_BUFFER_BINDINGS:
    case GL_MAX_VARYING_COMPONENTS:
    case GL_MAX_VERTEX_OUTPUT_COMPONENTS:
    case GL_MAX_VERTEX_UNIFORM_BLOCKS:
    case GL_MAX_VERTEX_UNIFORM_COMPONENTS:
    case GL_MIN_PROGRAM_TEXEL_OFFSET:
    case GL_MAX_VIEWS_OVR:
    {
      // return an int
      GLint param;
      glGetIntegerv(name, &param);
      info.GetReturnValue().Set(JS_INT(param));
      break;
    }
    case GL_DEPTH_CLEAR_VALUE:
    case GL_LINE_WIDTH:
    case GL_POLYGON_OFFSET_FACTOR:
    case GL_POLYGON_OFFSET_UNITS:
    case GL_SAMPLE_COVERAGE_VALUE:
    case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT:
    {
      // return a float
      GLfloat params;
      glGetFloatv(name, &params);
      info.GetReturnValue().Set(JS_FLOAT(params));
      break;
    }
    case GL_RENDERER:
    case GL_SHADING_LANGUAGE_VERSION:
    case GL_VENDOR:
    case GL_EXTENSIONS:
    {
      // return a string
      char *params = (char *)glGetString(name);

      if (params != NULL) {
        info.GetReturnValue().Set(JS_STR(params));
      } else {
        info.GetReturnValue().Set(Nan::Undefined());
      }

      break;
    }
    case GL_VERSION:
    {
      Local<Value> constructorName = JS_OBJ(info.This()->Get(JS_STR("constructor")))->Get(JS_STR("name"));
      if (constructorName->StrictEquals(JS_STR("WebGL2RenderingContext"))) {
        info.GetReturnValue().Set(JS_STR("WebGL 2"));
      } else {
        info.GetReturnValue().Set(JS_STR("WebGL 1"));
      }
      break;
    }
    case GL_MAX_VIEWPORT_DIMS:
    {
      // return a int32[2]
      GLint params[2];
      glGetIntegerv(name, params);
      info.GetReturnValue().Set(createTypedArray<Int32Array>(2, params));
      break;
    }
    case GL_SCISSOR_BOX:
    case GL_VIEWPORT:
    {
      // return a int32[4]
      GLint params[4];
      glGetIntegerv(name, params);
      info.GetReturnValue().Set(createTypedArray<Int32Array>(4, params));
      break;
    }
    case GL_ALIASED_LINE_WIDTH_RANGE:
    case GL_ALIASED_POINT_SIZE_RANGE:
    case GL_DEPTH_RANGE:
    {
      // return a float[2]
      GLfloat params[2];
      glGetFloatv(name, params);
      info.GetReturnValue().Set(createTypedArray<Float32Array>(2, params));
      break;
    }
    case GL_BLEND_COLOR:
    case GL_COLOR_CLEAR_VALUE:
    {
      // return a float[4]
      GLfloat params[4];
      glGetFloatv(name, params);
      info.GetReturnValue().Set(createTypedArray<Float32Array>(4, params));
      break;
    }
    case GL_COLOR_WRITEMASK:
    {
      // return a boolean[4]
      GLboolean params[4];
      glGetBooleanv(name, params);

      Local<Array> arr = Nan::New<Array>(4);
      arr->Set(0,JS_BOOL(params[0]==1));
      arr->Set(1,JS_BOOL(params[1]==1));
      arr->Set(2,JS_BOOL(params[2]==1));
      arr->Set(3,JS_BOOL(params[3]==1));
      info.GetReturnValue().Set(arr);
      break;
    }
    case GL_ARRAY_BUFFER_BINDING:
    case GL_ELEMENT_ARRAY_BUFFER_BINDING:
    case GL_FRAMEBUFFER_BINDING: // == GL_DRAW_FRAMEBUFFER_BINDING
    case GL_READ_FRAMEBUFFER_BINDING:
    case GL_RENDERBUFFER_BINDING:
    case GL_TEXTURE_BINDING_2D:
    case GL_TEXTURE_BINDING_CUBE_MAP:
    case GL_ACTIVE_TEXTURE:
    case GL_CURRENT_PROGRAM:
    case GL_VERTEX_ARRAY_BINDING:
    {
      GLint param;
      glGetIntegerv(name, &param);

      if (param != 0) {
        Local<Object> object = Nan::New<Object>();
        object->Set(JS_STR("id"), JS_INT(param));
        info.GetReturnValue().Set(object);
      } else {
        info.GetReturnValue().Set(Nan::Null());
      }
      break;
    }
    case GL_COMPRESSED_TEXTURE_FORMATS:
    {
      GLint numFormats;
      glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numFormats);

      unique_ptr<GLint[]> params(new GLint[numFormats]);
      glGetIntegerv(name, params.get());
      info.GetReturnValue().Set(createTypedArray<Uint32Array>(numFormats, (uint32_t *)params.get()));
      break;
    }
    case UNPACK_FLIP_Y_WEBGL: {
      WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
      // return a boolean
      const bool flipY = gl->HasPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) ? (bool)gl->GetPixelStoreiBinding(UNPACK_FLIP_Y_WEBGL) : false;
      info.GetReturnValue().Set(JS_BOOL(flipY));
      break;
    }
    case UNPACK_PREMULTIPLY_ALPHA_WEBGL: {
      WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
      // return a boolean
      const bool premultiplyAlpha = gl->HasPixelStoreiBinding(UNPACK_PREMULTIPLY_ALPHA_WEBGL) ? (bool)gl->GetPixelStoreiBinding(UNPACK_PREMULTIPLY_ALPHA_WEBGL) : true;
      info.GetReturnValue().Set(JS_BOOL(premultiplyAlpha));
      break;
    }
    case MAX_CLIENT_WAIT_TIMEOUT_WEBGL:
    case GL_MAX_SERVER_WAIT_TIMEOUT:
    {
      info.GetReturnValue().Set(JS_INT(0));
      break;
    }
    case GL_MAX_ELEMENT_INDEX:
    {
      GLint64 elementIndex = -1;
      glGetInteger64v(GL_MAX_ELEMENT_INDEX, &elementIndex);
      info.GetReturnValue().Set(JS_INT((GLuint)elementIndex));
      break;
    }
    default: {
      // Nan::ThrowError("WebGLRenderingContext::GetParameter: invalid parameter");
      info.GetReturnValue().Set(Nan::Null());
      break;
    }
  }

  //info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::GetBufferParameter) {
  GLenum target = TO_INT32(info[0]);
  GLenum pname = TO_INT32(info[1]);
  GLint params;

  glGetBufferParameteriv(target, pname, &params);

  info.GetReturnValue().Set(JS_INT(params));
}

NAN_METHOD(WebGLRenderingContext::GetFramebufferAttachmentParameter) {
  GLenum target = TO_INT32(info[0]);
  GLenum attachment = TO_INT32(info[1]);
  GLenum pname = TO_INT32(info[2]);
  GLint params;

  glGetFramebufferAttachmentParameteriv(target,attachment, pname, &params);

  info.GetReturnValue().Set(JS_INT(params));
}

NAN_METHOD(WebGLRenderingContext::GetProgramInfoLog) {
  GLuint program = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  char Error[1024];
  int Len;

  glGetProgramInfoLog(program, sizeof(Error), &Len, Error);

  info.GetReturnValue().Set(JS_STR(Error, Len));
}

NAN_METHOD(WebGLRenderingContext::GetRenderbufferParameter) {
  int target = TO_INT32(info[0]);
  int pname = TO_INT32(info[1]);
  int value;

  glGetRenderbufferParameteriv(target, pname, &value);

  info.GetReturnValue().Set(JS_INT(value));
}

NAN_METHOD(WebGLRenderingContext::GetUniform) {
  GLuint program = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint location = TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id")));

  char name[1024];
  GLsizei length = 0;
  GLsizei size;
  GLenum type;

  glGetActiveUniform(program, location, sizeof(name), &length, &size, &type, name);

  if (length > 0) {
    GLfloat fData[16];
    GLint iData[16];
    GLuint uiData[16];
    // GLdouble dData[16];
    switch(type) {
      /* case GL_DOUBLE:
      case GL_DOUBLE_VEC2:
      case GL_DOUBLE_VEC3:
      case GL_DOUBLE_VEC4:
        glGetUniformdv(program, location, dData);
        break; */
      case GL_FLOAT:
      case GL_FLOAT_VEC2:
      case GL_FLOAT_VEC3:
      case GL_FLOAT_VEC4:
      case GL_FLOAT_MAT2:
      case GL_FLOAT_MAT3:
      case GL_FLOAT_MAT4:
      case GL_FLOAT_MAT2x3:
      case GL_FLOAT_MAT2x4:
      case GL_FLOAT_MAT3x2:
      case GL_FLOAT_MAT3x4:
      case GL_FLOAT_MAT4x2:
      case GL_FLOAT_MAT4x3:
        glGetUniformfv(program, location, fData);
        break;
      case GL_INT:
      case GL_INT_VEC2:
      case GL_INT_VEC3:
      case GL_INT_VEC4:
      case GL_BOOL:
      case GL_BOOL_VEC2:
      case GL_BOOL_VEC3:
      case GL_BOOL_VEC4:
        glGetUniformiv(program, location, iData);
        break;
      case GL_UNSIGNED_INT:
      case GL_UNSIGNED_INT_VEC2:
      case GL_UNSIGNED_INT_VEC3:
      case GL_UNSIGNED_INT_VEC4:
        glGetUniformuiv(program, location, uiData);
        break;
      case GL_SAMPLER_2D:
      case GL_SAMPLER_CUBE:
      case GL_SAMPLER_3D:
      case GL_SAMPLER_2D_SHADOW:
      case GL_SAMPLER_2D_ARRAY:
      case GL_SAMPLER_2D_ARRAY_SHADOW:
      case GL_SAMPLER_CUBE_SHADOW:
      case GL_INT_SAMPLER_2D:
      case GL_INT_SAMPLER_3D:
      case GL_INT_SAMPLER_CUBE:
      case GL_INT_SAMPLER_2D_ARRAY:
      case GL_UNSIGNED_INT_SAMPLER_2D:
      case GL_UNSIGNED_INT_SAMPLER_3D:
      case GL_UNSIGNED_INT_SAMPLER_CUBE:
      case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
        glGetUniformiv(program, location, iData);
        break;
      default:
        Nan::ThrowError("Not implemented");
        break;
    }
    switch(type) {
      case GL_FLOAT: info.GetReturnValue().Set(fData[0]); break;
      case GL_FLOAT_VEC2: info.GetReturnValue().Set(createTypedArray<Float32Array>(2, fData)); break;
      case GL_FLOAT_VEC3: info.GetReturnValue().Set(createTypedArray<Float32Array>(3, fData)); break;
      case GL_FLOAT_VEC4: info.GetReturnValue().Set(createTypedArray<Float32Array>(4, fData)); break;
      case GL_FLOAT_MAT2: info.GetReturnValue().Set(createTypedArray<Float32Array>(2*2, fData)); break;
      case GL_FLOAT_MAT3: info.GetReturnValue().Set(createTypedArray<Float32Array>(3*3, fData)); break;
      case GL_FLOAT_MAT4: info.GetReturnValue().Set(createTypedArray<Float32Array>(4*4, fData)); break;
      case GL_FLOAT_MAT2x3: info.GetReturnValue().Set(createTypedArray<Float32Array>(2*3, fData)); break;
      case GL_FLOAT_MAT2x4: info.GetReturnValue().Set(createTypedArray<Float32Array>(2*4, fData)); break;
      case GL_FLOAT_MAT3x2: info.GetReturnValue().Set(createTypedArray<Float32Array>(3*2, fData)); break;
      case GL_FLOAT_MAT3x4: info.GetReturnValue().Set(createTypedArray<Float32Array>(3*4, fData)); break;
      case GL_FLOAT_MAT4x2: info.GetReturnValue().Set(createTypedArray<Float32Array>(4*2, fData)); break;
      case GL_FLOAT_MAT4x3: info.GetReturnValue().Set(createTypedArray<Float32Array>(4*3, fData)); break;
      case GL_INT: info.GetReturnValue().Set(iData[0]); break;
      case GL_INT_VEC2: info.GetReturnValue().Set(createTypedArray<Int32Array>(2, iData)); break;
      case GL_INT_VEC3: info.GetReturnValue().Set(createTypedArray<Int32Array>(3, iData)); break;
      case GL_INT_VEC4: info.GetReturnValue().Set(createTypedArray<Int32Array>(4, iData)); break;
      case GL_BOOL: info.GetReturnValue().Set(!!iData[0]); break;
      case GL_UNSIGNED_INT: info.GetReturnValue().Set(uiData[0]); break;
      case GL_UNSIGNED_INT_VEC2: info.GetReturnValue().Set(createTypedArray<Uint32Array>(2, uiData)); break;
      case GL_UNSIGNED_INT_VEC3: info.GetReturnValue().Set(createTypedArray<Uint32Array>(3, uiData)); break;
      case GL_UNSIGNED_INT_VEC4: info.GetReturnValue().Set(createTypedArray<Uint32Array>(4, uiData)); break;
      case GL_SAMPLER_2D:
      case GL_SAMPLER_CUBE:
      case GL_SAMPLER_3D:
      case GL_SAMPLER_2D_SHADOW:
      case GL_SAMPLER_2D_ARRAY:
      case GL_SAMPLER_2D_ARRAY_SHADOW:
      case GL_SAMPLER_CUBE_SHADOW:
      case GL_INT_SAMPLER_2D:
      case GL_INT_SAMPLER_3D:
      case GL_INT_SAMPLER_CUBE:
      case GL_INT_SAMPLER_2D_ARRAY:
      case GL_UNSIGNED_INT_SAMPLER_2D:
      case GL_UNSIGNED_INT_SAMPLER_3D:
      case GL_UNSIGNED_INT_SAMPLER_CUBE:
      case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
      info.GetReturnValue().Set(iData[0]);
      break;
      default:
        Nan::ThrowError("Not implemented");
        break;
    }
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGLRenderingContext::GetVertexAttrib) {
  GLuint index = TO_INT32(info[0]);
  GLuint pname = TO_INT32(info[1]);
  GLint value;

  switch (pname) {
    case GL_VERTEX_ATTRIB_ARRAY_ENABLED:
    case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
      glGetVertexAttribiv(index, pname, &value);
      info.GetReturnValue().Set(JS_BOOL(static_cast<bool>(value)));
      break;
    case GL_VERTEX_ATTRIB_ARRAY_SIZE:
    case GL_VERTEX_ATTRIB_ARRAY_STRIDE:
    case GL_VERTEX_ATTRIB_ARRAY_TYPE:
      glGetVertexAttribiv(index, pname, &value);
      info.GetReturnValue().Set(JS_INT(value));
      break;
    case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
      glGetVertexAttribiv(index, pname, &value);
      info.GetReturnValue().Set(JS_INT(value));
      break;
    case GL_CURRENT_VERTEX_ATTRIB: {
      float vertex_attribs[4];
      glGetVertexAttribfv(index, pname, vertex_attribs);
      info.GetReturnValue().Set(createTypedArray<Float32Array>(4, vertex_attribs));
      break;
    }
    default: {
      Nan::ThrowError("GetVertexAttrib: Invalid Enum");
      break;
    }
  }
}

NAN_METHOD(WebGLRenderingContext::GetIndexedParameter) {
  GLenum target = TO_UINT32(info[0]);
  GLuint index = TO_UINT32(info[1]);

  switch (target) {
    case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: {
      GLint data;
      glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, index, &data);
      if (data != 0) {
        Local<Object> result = Nan::New<Object>();
        result->Set(JS_STR("id"), JS_INT(data));
        info.GetReturnValue().Set(result);
      } else {
        info.GetReturnValue().Set(Nan::Null());
      }
      break;
    }
    case GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: {
      GLint64 data;
      glGetInteger64i_v(GL_TRANSFORM_FEEDBACK_BUFFER_SIZE, index, &data);
      info.GetReturnValue().Set(JS_INT(static_cast<GLint>(data)));
      break;
    }
    case GL_TRANSFORM_FEEDBACK_BUFFER_START: {
      GLint64 data;
      glGetInteger64i_v(GL_TRANSFORM_FEEDBACK_BUFFER_START, index, &data);
      info.GetReturnValue().Set(JS_INT(static_cast<GLint>(data)));
      break;
    }
    case GL_UNIFORM_BUFFER_BINDING: {
      GLint data;
      glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, index, &data);
      if (data != 0) {
        Local<Object> result = Nan::New<Object>();
        result->Set(JS_STR("id"), JS_INT(data));
        info.GetReturnValue().Set(result);
      } else {
        info.GetReturnValue().Set(Nan::Null());
      }
      break;
    }
    case GL_UNIFORM_BUFFER_SIZE: {
      GLint64 data;
      glGetInteger64i_v(GL_UNIFORM_BUFFER_SIZE, index, &data);
      info.GetReturnValue().Set(JS_INT(static_cast<GLint>(data)));
      break;
    }
    case GL_UNIFORM_BUFFER_START: {
      GLint64 data;
      glGetInteger64i_v(GL_UNIFORM_BUFFER_START, index, &data);
      info.GetReturnValue().Set(JS_INT(static_cast<GLint>(data)));
      break;
    }
    default: {
      info.GetReturnValue().Set(Nan::Null());
      break;
    }
  }
}

NAN_METHOD(WebGLRenderingContext::GetFragDataLocation) {
  GLuint program = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  Local<String> name = Local<String>::Cast(info[1]);

  Nan::Utf8String nameUtf8String(Local<String>::Cast(info[0]));
  const char *nameV = *nameUtf8String;

  GLint result = glGetFragDataLocation(program, nameV);
  info.GetReturnValue().Set(result);
}

const char *webglExtensions[] = {
  "ANGLE_instanced_arrays",
  "EXT_blend_minmax",
  "EXT_color_buffer_float",
  "EXT_color_buffer_half_float",
  "EXT_disjoint_timer_query",
  "EXT_frag_depth",
  "EXT_sRGB",
  "EXT_shader_texture_lod",
  "EXT_texture_filter_anisotropic",
  "OES_element_index_uint",
  "OES_standard_derivatives",
  "OES_texture_float",
  "OES_texture_float_linear",
  "OES_texture_half_float",
  "OES_texture_half_float_linear",
  "OES_vertex_array_object",
  "OVR_multiview_multisampled_render_to_texture",
  "OVR_multiview2",
  "WEBGL_color_buffer_float",
  "WEBGL_compressed_texture_astc",
  "WEBGL_compressed_texture_atc",
  "WEBGL_compressed_texture_etc",
  "WEBGL_compressed_texture_etc1",
  "WEBGL_compressed_texture_pvrtc",
  "WEBGL_compressed_texture_s3tc",
  "WEBGL_compressed_texture_s3tc_srgb",
  "WEBGL_debug_renderer_info",
  "WEBGL_debug_shaders",
  "WEBGL_depth_texture",
  "WEBGL_draw_buffers",
  "WEBGL_lose_context",
};
NAN_METHOD(WebGLRenderingContext::GetSupportedExtensions) {

  int numExtensions = sizeof(webglExtensions)/sizeof(webglExtensions[0]);
  Local<Array> result = Nan::New<Array>(numExtensions);
  for (GLint i = 0; i < numExtensions; i++) {
    // char *extension = (char *)glGetStringi(GL_EXTENSIONS, i);
    result->Set(i, JS_STR(webglExtensions[i]));
  }

  info.GetReturnValue().Set(result);
}

// TODO GetExtension(name) return the extension name if found, should be an object...
NAN_METHOD(WebGLRenderingContext::GetExtension) {
  Nan::Utf8String name(Local<String>::Cast(info[0]));
  char *sname = *name;

  if (
    strcmp(sname, "OES_texture_float") == 0 ||
    strcmp(sname, "OES_texture_float_linear") == 0 ||
    strcmp(sname, "OES_texture_half_float_linear") == 0 ||
    strcmp(sname, "OES_element_index_uint") == 0 ||
    strcmp(sname, "EXT_shader_texture_lod") == 0 ||
    strcmp(sname, "EXT_frag_depth") == 0
  ) {
    info.GetReturnValue().Set(Object::New(Isolate::GetCurrent()));
  } else if (strcmp(sname, "OES_texture_half_float") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "HALF_FLOAT_OES"), Number::New(Isolate::GetCurrent(), GL_HALF_FLOAT_OES));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "OES_standard_derivatives") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "FRAGMENT_SHADER_DERIVATIVE_HINT_OES"), Number::New(Isolate::GetCurrent(), GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "WEBGL_depth_texture") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "UNSIGNED_INT_24_8_WEBGL"), Number::New(Isolate::GetCurrent(), GL_UNSIGNED_INT_24_8_OES));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "EXT_texture_filter_anisotropic") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "MAX_TEXTURE_MAX_ANISOTROPY_EXT"), Number::New(Isolate::GetCurrent(), GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "TEXTURE_MAX_ANISOTROPY_EXT"), Number::New(Isolate::GetCurrent(), GL_TEXTURE_MAX_ANISOTROPY_EXT));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "WEBGL_compressed_texture_s3tc") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGB_S3TC_DXT1_EXT"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGB_S3TC_DXT1_EXT));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGBA_S3TC_DXT1_EXT"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGBA_S3TC_DXT1_EXT));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGBA_S3TC_DXT3_EXT"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGBA_S3TC_DXT3_EXT));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGBA_S3TC_DXT5_EXT"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGBA_S3TC_DXT5_EXT));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "WEBGL_compressed_texture_pvrtc") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGB_PVRTC_4BPPV1_IMG"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGB_PVRTC_2BPPV1_IMG"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG));
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"), Number::New(Isolate::GetCurrent(), GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "WEBGL_compressed_texture_etc1") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "COMPRESSED_RGB_ETC1_WEBGL"), Number::New(Isolate::GetCurrent(), GL_ETC1_RGB8_OES));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "ANGLE_instanced_arrays") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(String::NewFromUtf8(Isolate::GetCurrent(), "GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE"), Number::New(Isolate::GetCurrent(), GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE));
    result->Set(JS_STR("context"), info.This());
    Nan::SetMethod(result, "drawArraysInstancedANGLE", DrawArraysInstancedANGLE);
    Nan::SetMethod(result, "drawElementsInstancedANGLE", DrawElementsInstancedANGLE);
    Nan::SetMethod(result, "vertexAttribDivisorANGLE", VertexAttribDivisorANGLE);
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "WEBGL_draw_buffers") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());

    result->Set(JS_STR("context"), info.This());
    Nan::SetMethod(result, "drawBuffersWEBGL", DrawBuffersWEBGL);

    result->Set(JS_STR("COLOR_ATTACHMENT0_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT0));
    result->Set(JS_STR("COLOR_ATTACHMENT1_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT1));
    result->Set(JS_STR("COLOR_ATTACHMENT2_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT2));
    result->Set(JS_STR("COLOR_ATTACHMENT3_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT3));
    result->Set(JS_STR("COLOR_ATTACHMENT4_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT4));
    result->Set(JS_STR("COLOR_ATTACHMENT5_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT5));
    result->Set(JS_STR("COLOR_ATTACHMENT6_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT6));
    result->Set(JS_STR("COLOR_ATTACHMENT7_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT7));
    result->Set(JS_STR("COLOR_ATTACHMENT8_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT8));
    result->Set(JS_STR("COLOR_ATTACHMENT9_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT9));
    result->Set(JS_STR("COLOR_ATTACHMENT10_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT10));
    result->Set(JS_STR("COLOR_ATTACHMENT11_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT11));
    result->Set(JS_STR("COLOR_ATTACHMENT12_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT12));
    result->Set(JS_STR("COLOR_ATTACHMENT13_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT13));
    result->Set(JS_STR("COLOR_ATTACHMENT14_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT14));
    result->Set(JS_STR("COLOR_ATTACHMENT15_WEBGL"), JS_INT(GL_COLOR_ATTACHMENT15));

    result->Set(JS_STR("DRAW_BUFFER0_WEBGL"), JS_INT(GL_DRAW_BUFFER0));
    result->Set(JS_STR("DRAW_BUFFER1_WEBGL"), JS_INT(GL_DRAW_BUFFER1));
    result->Set(JS_STR("DRAW_BUFFER2_WEBGL"), JS_INT(GL_DRAW_BUFFER2));
    result->Set(JS_STR("DRAW_BUFFER3_WEBGL"), JS_INT(GL_DRAW_BUFFER3));
    result->Set(JS_STR("DRAW_BUFFER4_WEBGL"), JS_INT(GL_DRAW_BUFFER4));
    result->Set(JS_STR("DRAW_BUFFER5_WEBGL"), JS_INT(GL_DRAW_BUFFER5));
    result->Set(JS_STR("DRAW_BUFFER6_WEBGL"), JS_INT(GL_DRAW_BUFFER6));
    result->Set(JS_STR("DRAW_BUFFER7_WEBGL"), JS_INT(GL_DRAW_BUFFER7));
    result->Set(JS_STR("DRAW_BUFFER8_WEBGL"), JS_INT(GL_DRAW_BUFFER8));
    result->Set(JS_STR("DRAW_BUFFER9_WEBGL"), JS_INT(GL_DRAW_BUFFER9));
    result->Set(JS_STR("DRAW_BUFFER10_WEBGL"), JS_INT(GL_DRAW_BUFFER10));
    result->Set(JS_STR("DRAW_BUFFER11_WEBGL"), JS_INT(GL_DRAW_BUFFER11));
    result->Set(JS_STR("DRAW_BUFFER12_WEBGL"), JS_INT(GL_DRAW_BUFFER12));
    result->Set(JS_STR("DRAW_BUFFER13_WEBGL"), JS_INT(GL_DRAW_BUFFER13));
    result->Set(JS_STR("DRAW_BUFFER14_WEBGL"), JS_INT(GL_DRAW_BUFFER14));
    result->Set(JS_STR("DRAW_BUFFER15_WEBGL"), JS_INT(GL_DRAW_BUFFER15));

    result->Set(JS_STR("MAX_COLOR_ATTACHMENTS_WEBGL"), JS_INT(GL_MAX_COLOR_ATTACHMENTS));
    result->Set(JS_STR("MAX_DRAW_BUFFERS_WEBGL"), JS_INT(GL_MAX_DRAW_BUFFERS));

    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "WEBGL_debug_renderer_info") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(JS_STR("UNMASKED_RENDERER_WEBGL"), JS_INT(GL_RENDERER));
    result->Set(JS_STR("UNMASKED_VENDOR_WEBGL"), JS_INT(GL_VENDOR));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "EXT_color_buffer_float") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "EXT_color_buffer_half_float") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(JS_STR("FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT"), JS_INT(GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT));
    result->Set(JS_STR("RGB16F_EXT"), JS_INT(GL_RGB16F_EXT));
    result->Set(JS_STR("RGBA16F_EXT"), JS_INT(GL_RGBA16F_EXT));
    result->Set(JS_STR("UNSIGNED_NORMALIZED_EXT"), JS_INT(GL_UNSIGNED_NORMALIZED_EXT));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "OVR_multiview2") == 0) {
    // Add constants: khronos.org/registry/webgl/extensions/OVR_multiview2/
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(JS_STR("FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR"), JS_INT(0x9630));
    result->Set(JS_STR("FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR"), JS_INT(0x9632));
    result->Set(JS_STR("MAX_VIEWS_OVR"), JS_INT(0x9631));
    result->Set(JS_STR("FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR"), JS_INT(0x9633));
    Nan::SetMethod(result, "framebufferTextureMultiviewOVR", FramebufferTextureMultiviewOVR);
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "OVR_multiview_multisampled_render_to_texture") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    Nan::SetMethod(result, "FramebufferTextureMultisampleMultiviewOVR", FramebufferTextureMultisampleMultiviewOVR);
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "EXT_blend_minmax") == 0) {
    // Adds two constants: developer.mozilla.org/docs/Web/API/EXT_blend_minmax
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(JS_STR("MIN_EXT"), JS_INT(GL_MIN_EXT));
    result->Set(JS_STR("MAX_EXT"), JS_INT(GL_MAX_EXT));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "EXT_sRGB") == 0) {
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(JS_STR("FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT"), JS_INT(GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT));
    result->Set(JS_STR("SRGB8_ALPHA8_EXT"), JS_INT(GL_SRGB8_ALPHA8_EXT));
    result->Set(JS_STR("SRGB_ALPHA_EXT"), JS_INT(GL_SRGB_ALPHA_EXT));
    result->Set(JS_STR("SRGB_EXT"), JS_INT(GL_SRGB_EXT));
    info.GetReturnValue().Set(result);
  } else if (strcmp(sname, "OES_vertex_array_object") == 0) {
    // Same as other vertex array methods, but with the OES suffix for WebGL 1.
    Local<Object> result = Object::New(Isolate::GetCurrent());
    result->Set(JS_STR("context"), info.This());
    Nan::SetMethod(result, "createVertexArrayOES", CreateVertexArray);
    Nan::SetMethod(result, "deleteVertexArrayOES", DeleteVertexArray);
    Nan::SetMethod(result, "isVertexArrayOES", IsVertexArray);
    Nan::SetMethod(result, "bindVertexArrayOES", BindVertexArrayOES);
    info.GetReturnValue().Set(result);
  } else {
    info.GetReturnValue().Set(Null(Isolate::GetCurrent()));
  }
}

/* NAN_METHOD(WebGLRenderingContext::GetContextAttributes) {
  Local<Object> result = Object::New(Isolate::GetCurrent());
  result->Set(JS_STR("alpha"), JS_BOOL(true));
  result->Set(JS_STR("antialias"), JS_BOOL(true));
  result->Set(JS_STR("depth"), JS_BOOL(true));
  result->Set(JS_STR("failIfMajorPerformanceCaveat"), JS_BOOL(false));
  result->Set(JS_STR("premultipliedAlpha"), JS_BOOL(true));
  result->Set(JS_STR("preserveDrawingBuffer"), JS_BOOL(false));
  result->Set(JS_STR("stencil"), JS_BOOL(false));
  info.GetReturnValue().Set(result);
} */

NAN_METHOD(WebGLRenderingContext::CheckFramebufferStatus) {
  GLenum target = TO_INT32(info[0]);
  GLint ret = glCheckFramebufferStatus(target);

  info.GetReturnValue().Set(JS_INT(ret));
}

NAN_METHOD(WebGLRenderingContext::CreateVertexArray) {
  GLuint vao;
  glGenVertexArrays(1, &vao);

  Local<Object> vaoObject = Nan::New<Object>();
  vaoObject->Set(JS_STR("id"), JS_INT(vao));
  info.GetReturnValue().Set(vaoObject);
}

NAN_METHOD(WebGLRenderingContext::DeleteVertexArray) {
  GLuint vao = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glDeleteVertexArrays(1, &vao);

  // info.GetReturnValue().Set(Nan::Undefined());
}

NAN_METHOD(WebGLRenderingContext::BindVertexArray) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());
  GLuint vao = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : gl->defaultVao;

  glBindVertexArray(vao);

  gl->SetVertexArrayBinding(vao);
}

NAN_METHOD(WebGLRenderingContext::BindVertexArrayOES) {
  Local<Object> contextObj = Local<Object>::Cast(info.This()->Get(JS_STR("context")));

  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(contextObj);
  GLuint vao = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : gl->defaultVao;

  glBindVertexArray(vao);

  gl->SetVertexArrayBinding(vao);
}

NAN_METHOD(WebGLRenderingContext::FenceSync) {
  GLenum condition = TO_UINT32(info[0]);
  GLbitfield flags = TO_UINT32(info[1]);

  GLsync sync = (GLsync)glFenceSync(condition, flags);
  Local<Array> syncArray = pointerToArray(sync);

  Local<Object> syncObject = Nan::New<Object>();
  syncObject->Set(JS_STR("id"), syncArray);
  info.GetReturnValue().Set(syncObject);
}

NAN_METHOD(WebGLRenderingContext::DeleteSync) {
  Local<Array> syncArray = Local<Array>::Cast(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLsync sync = (GLsync)arrayToPointer(syncArray);

  glDeleteSync(sync);
}

NAN_METHOD(WebGLRenderingContext::ClientWaitSync) {
  Local<Array> syncArray = Local<Array>::Cast(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLsync sync = (GLsync)arrayToPointer(syncArray);
  GLbitfield flags = TO_UINT32(info[1]);
  double timeoutValue = TO_DOUBLE(info[2]);
  GLint64 timeout = *(GLint64 *)(&timeoutValue);

  GLenum ret = glClientWaitSync(sync, flags, timeout);

  info.GetReturnValue().Set(JS_INT(ret));
}

NAN_METHOD(WebGLRenderingContext::WaitSync) {
  Local<Array> syncArray = Local<Array>::Cast(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLsync sync = (GLsync)arrayToPointer(syncArray);
  GLbitfield flags = TO_UINT32(info[1]);
  double timeoutValue = TO_DOUBLE(info[2]);
  GLint64 timeout = *(GLint64 *)(&timeoutValue);

  glWaitSync(sync, flags, timeout);
}

NAN_METHOD(WebGLRenderingContext::GetSyncParameter) {
  Local<Array> syncArray = Local<Array>::Cast(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLsync sync = (GLsync)arrayToPointer(syncArray);
  GLbitfield pname = TO_UINT32(info[1]);

  GLint result = 0;
  GLsizei len;
  glGetSynciv(sync, pname, 1, &len, &result);

  info.GetReturnValue().Set(JS_INT(result));
}

// WebGL2RenderingContext

WebGL2RenderingContext::WebGL2RenderingContext() {}

WebGL2RenderingContext::~WebGL2RenderingContext() {}

std::pair<Local<Object>, Local<FunctionTemplate>> WebGL2RenderingContext::Initialize(Isolate *isolate, Local<FunctionTemplate> baseCtor) {
  // Nan::EscapableHandleScope scope;

  Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(WebGL2RenderingContext::New);

  ctor->Inherit(baseCtor);
  ctor->InstanceTemplate()->SetInternalFieldCount(1);
  ctor->SetClassName(JS_STR("WebGL2RenderingContext"));

  // prototype
  Local<ObjectTemplate> proto = ctor->PrototypeTemplate();

  Nan::SetMethod(proto, "createQuery", glCallWrap<CreateQuery>);
  Nan::SetMethod(proto, "beginQuery", glCallWrap<BeginQuery>);
  Nan::SetMethod(proto, "endQuery", glCallWrap<EndQuery>);
  Nan::SetMethod(proto, "getQuery", glCallWrap<GetQuery>);
  Nan::SetMethod(proto, "getQueryParameter", glCallWrap<GetQueryParameter>);
  Nan::SetMethod(proto, "isQuery", glCallWrap<IsQuery>);
  Nan::SetMethod(proto, "deleteQuery", glCallWrap<DeleteQuery>);

  Nan::SetMethod(proto, "createTransformFeedback", glCallWrap<CreateTransformFeedback>);
  Nan::SetMethod(proto, "deleteTransformFeedback", glCallWrap<DeleteTransformFeedback>);
  Nan::SetMethod(proto, "isTransformFeedback", glCallWrap<IsTransformFeedback>);
  Nan::SetMethod(proto, "bindTransformFeedback", glCallWrap<BindTransformFeedback>);
  Nan::SetMethod(proto, "beginTransformFeedback", glCallWrap<BeginTransformFeedback>);
  Nan::SetMethod(proto, "endTransformFeedback", glCallWrap<EndTransformFeedback>);
  Nan::SetMethod(proto, "transformFeedbackVaryings", glCallWrap<TransformFeedbackVaryings>);
  Nan::SetMethod(proto, "getTransformFeedbackVarying", glCallWrap<GetTransformFeedbackVarying>);
  Nan::SetMethod(proto, "pauseTransformFeedback", glCallWrap<PauseTransformFeedback>);
  Nan::SetMethod(proto, "resumeTransformFeedback", glCallWrap<ResumeTransformFeedback>);

  Nan::SetMethod(proto, "getInternalformatParameter", glCallWrap<GetInternalformatParameter>);

  Nan::SetMethod(proto, "createSampler", glCallWrap<CreateSampler>);
  Nan::SetMethod(proto, "deleteSampler", glCallWrap<DeleteSampler>);
  Nan::SetMethod(proto, "isSampler", glCallWrap<IsSampler>);
  Nan::SetMethod(proto, "bindSampler", glCallWrap<BindSampler>);
  Nan::SetMethod(proto, "samplerParameteri", glCallWrap<SamplerParameteri>);
  Nan::SetMethod(proto, "samplerParameterf", glCallWrap<SamplerParameterf>);
  Nan::SetMethod(proto, "getSamplerParameter", glCallWrap<GetSamplerParameter>);

  Local<Function> ctorFn = Nan::GetFunction(ctor).ToLocalChecked();
  setGlConstants(ctorFn);

  return std::pair<Local<Object>, Local<FunctionTemplate>>(ctorFn, ctor);
}

NAN_METHOD(WebGL2RenderingContext::New) {
  WebGL2RenderingContext *gl2 = new WebGL2RenderingContext();
  Local<Object> gl2Obj = info.This();
  gl2->Wrap(gl2Obj);

  info.GetReturnValue().Set(gl2Obj);
}

// reference used https://www.khronos.org/registry/OpenGL-Refpages/es3.0/
NAN_METHOD(WebGL2RenderingContext::CreateQuery) { // adapted from CreateBuffer
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint queryId;
  glGenQueries(1, &queryId);

  gl->objectCache.queries.insert(queryId);

  Local<Object> queryObject = Nan::New<Object>();
  queryObject->Set(JS_STR("id"), JS_INT(queryId));
  info.GetReturnValue().Set(queryObject);
}

NAN_METHOD(WebGL2RenderingContext::BeginQuery) { // adapted from BindBuffer
  GLenum target = TO_INT32(info[0]);
  GLuint query = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : 0;

  glBeginQuery(target, query);
}

NAN_METHOD(WebGL2RenderingContext::EndQuery) {
  GLenum target = TO_INT32(info[0]);
  glEndQuery(target);
}

NAN_METHOD(WebGL2RenderingContext::GetQuery) {
  GLenum target = TO_INT32(info[0]);
  GLenum pname = TO_INT32(info[1]);
  GLint value;

  glGetQueryiv(target, pname, &value);
  if (glGetError() == GL_NO_ERROR) {
    Local<Object> queryObject = Nan::New<Object>();
    queryObject->Set(JS_STR("id"), JS_INT(value));

    info.GetReturnValue().Set(queryObject);
  }
  else info.GetReturnValue().Set(Nan::Null());
}

NAN_METHOD(WebGL2RenderingContext::GetQueryParameter) { // adapted from GetProgramParameter
  GLuint queryId = TO_INT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLenum pname = TO_INT32(info[1]);

  switch (pname) {
    case GL_QUERY_RESULT_AVAILABLE: {
      GLuint value;
      glGetQueryObjectuiv(queryId, pname, &value);
      bool result = value != 0;
      info.GetReturnValue().Set(JS_BOOL(result));
      break;
    }
    case GL_QUERY_RESULT: {
      GLuint value;
      glGetQueryObjectuiv(queryId, pname, &value);
      info.GetReturnValue().Set(JS_INT(value));
      break;
    }
    default: {
      Nan::ThrowTypeError("GetQueryParameter: Invalid Enum");
      break;
    }
  }
}

NAN_METHOD(WebGL2RenderingContext::IsQuery) { // adapted from IsVertexArray
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsQuery(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGL2RenderingContext::DeleteQuery) { // adapted from DeleteBuffer
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint query = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  gl->objectCache.queries.erase(query);

  glDeleteQueries(1, &query);
}

NAN_METHOD(WebGL2RenderingContext::CreateTransformFeedback) {
  GLuint transformId;
  glGenTransformFeedbacks(1, &transformId);

  Local<Object> transformObject = Nan::New<Object>();
  transformObject->Set(JS_STR("id"), JS_INT(transformId));
  info.GetReturnValue().Set(transformObject);
}

NAN_METHOD(WebGL2RenderingContext::DeleteTransformFeedback) {
  GLuint transform = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  glDeleteTransformFeedbacks(1, &transform);
}

NAN_METHOD(WebGL2RenderingContext::IsTransformFeedback) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsTransformFeedback(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGL2RenderingContext::BindTransformFeedback) {
  GLenum target = TO_INT32(info[0]);
  GLuint transform = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : 0;

  glBindTransformFeedback(target, transform);
}

NAN_METHOD(WebGL2RenderingContext::BeginTransformFeedback) {
  GLenum primitiveMode = TO_INT32(info[0]);
  glBeginTransformFeedback(primitiveMode);
}

NAN_METHOD(WebGL2RenderingContext::EndTransformFeedback) {
  glEndTransformFeedback();
}

NAN_METHOD(WebGL2RenderingContext::TransformFeedbackVaryings) {
  GLuint program = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));

  Local<Array> jsVaryings = Local<Array>::Cast(info[1]);
  GLsizei count = jsVaryings->Length();

  char **varyings = new char*[count];

  for (int i = 0; i < count; i++) {
    Nan::Utf8String v(Local<String>::Cast(jsVaryings->Get(i)));
    varyings[i] = *v;
  }

  GLenum bufferMode = TO_INT32(info[2]);

  glTransformFeedbackVaryings(program, count, varyings, bufferMode);
  delete [] varyings;
}

NAN_METHOD(WebGL2RenderingContext::GetTransformFeedbackVarying) {
  GLuint program = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLuint index = TO_INT32(info[1]);

  char name[1024];
  GLsizei length = 0;
  GLsizei size;
  GLenum type;

  glGetTransformFeedbackVarying(program, index, sizeof(name), &length, &size, &type, name);
  if (length > 0) {
    Local<Object> activeInfo = Nan::New<Object>();
    activeInfo->Set(JS_STR("name"), JS_STR(name));
    activeInfo->Set(JS_STR("size"), JS_INT(size));
    activeInfo->Set(JS_STR("type"), JS_INT(type));

    info.GetReturnValue().Set(activeInfo);
  }
  else info.GetReturnValue().Set(Nan::Null());
}

NAN_METHOD(WebGL2RenderingContext::PauseTransformFeedback) {
  glPauseTransformFeedback();
}

NAN_METHOD(WebGL2RenderingContext::ResumeTransformFeedback) {
  glResumeTransformFeedback();
}

NAN_METHOD(WebGL2RenderingContext::GetInternalformatParameter) {
  GLenum target = TO_UINT32(info[0]);
  GLenum internalformat = TO_UINT32(info[1]);
  GLenum pname = TO_UINT32(info[2]);

  if (pname == GL_SAMPLES) {
    GLint numSamples = 0;
    glGetInternalformativ(target, internalformat, GL_NUM_SAMPLE_COUNTS, 1, &numSamples);

    GLsizei bufSize = numSamples;
    Local<ArrayBuffer> paramsArrayBuffer = ArrayBuffer::New(Isolate::GetCurrent(), bufSize * sizeof(GLint));
    Local<Int32Array> paramsInt32Array = Int32Array::New(paramsArrayBuffer, 0, bufSize);
    if (numSamples > 0) {
      glGetInternalformativ(target, internalformat, GL_SAMPLES, bufSize, (GLint *)paramsArrayBuffer->GetContents().Data());
    }

    info.GetReturnValue().Set(paramsInt32Array);
  } else {
    info.GetReturnValue().Set(Nan::Null());
  }
}

NAN_METHOD(WebGL2RenderingContext::CreateSampler) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint samplerId;
  glGenSamplers(1, &samplerId);

  gl->objectCache.samplers.insert(samplerId);

  Local<Object> samplerObject = Nan::New<Object>();
  samplerObject->Set(JS_STR("id"), JS_INT(samplerId));
  info.GetReturnValue().Set(samplerObject);
}

NAN_METHOD(WebGL2RenderingContext::DeleteSampler) {
  WebGLRenderingContext *gl = ObjectWrap::Unwrap<WebGLRenderingContext>(info.This());

  GLuint sampler = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;

  gl->objectCache.samplers.erase(sampler);

  glDeleteSamplers(1, &sampler);
}

NAN_METHOD(WebGL2RenderingContext::IsSampler) {
  if (info[0]->IsObject()) {
    GLuint arg = info[0]->IsObject() ? TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id"))) : 0;
    bool ret = glIsSampler(arg);

    info.GetReturnValue().Set(JS_BOOL(ret));
  } else {
    info.GetReturnValue().Set(Nan::New<Boolean>(false));
  }
}

NAN_METHOD(WebGL2RenderingContext::BindSampler) {
  GLuint unit = TO_UINT32(info[0]);
  GLuint sampler = info[1]->IsObject() ? TO_UINT32(JS_OBJ(info[1])->Get(JS_STR("id"))) : 0;

  glBindSampler(unit, sampler);
}

NAN_METHOD(WebGL2RenderingContext::SamplerParameteri) {
  GLuint sampler = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLenum pname = TO_UINT32(info[1]);
  GLint param = TO_INT32(info[2]);

  glSamplerParameteri(sampler, pname, param);
}

NAN_METHOD(WebGL2RenderingContext::SamplerParameterf) {
  GLuint sampler = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLenum pname = TO_INT32(info[1]);
  GLfloat param = TO_FLOAT(info[2]);

  glSamplerParameterf(sampler, pname, param);
}

NAN_METHOD(WebGL2RenderingContext::GetSamplerParameter) {
  GLuint sampler = TO_UINT32(JS_OBJ(info[0])->Get(JS_STR("id")));
  GLenum pname = TO_UINT32(info[1]);

  switch (pname) {
    case GL_TEXTURE_MIN_LOD:
    case GL_TEXTURE_MAX_LOD:
      GLfloat fValue;
      glGetSamplerParameterfv(sampler, pname, &fValue);
      info.GetReturnValue().Set(JS_FLOAT(fValue));
      break;
    case GL_TEXTURE_COMPARE_FUNC:
    case GL_TEXTURE_COMPARE_MODE:
    case GL_TEXTURE_MAG_FILTER:
    case GL_TEXTURE_MIN_FILTER:
    case GL_TEXTURE_WRAP_R:
    case GL_TEXTURE_WRAP_S:
    case GL_TEXTURE_WRAP_T:
      GLint iValue;
      glGetSamplerParameteriv(sampler, pname, &iValue);
      info.GetReturnValue().Set(JS_INT(iValue));
      break;
  }
}

/* struct GLObj {
  GLObjectType type;
  GLuint obj;
  GLObj(GLObjectType type, GLuint obj) {
    this->type=type;
    this->obj=obj;
  }
};

vector<GLObj*> globjs;
static bool atExit=false;

void registerGLObj(GLObjectType type, GLuint obj) {
  globjs.push_back(new GLObj(type,obj));
}


void unregisterGLObj(GLuint obj) {
  if(atExit) return;

  vector<GLObj*>::iterator it = globjs.begin();
  while(globjs.size() && it != globjs.end()) {
    GLObj *globj=*it;
    if(globj->obj==obj) {
      delete globj;
      globjs.erase(it);
      break;
    }
    ++it;
  }
}

void AtExit() {
  atExit=true;
  //glFinish();

  vector<GLObj*>::iterator it;

  #ifdef LOGGING
  cout<<"WebGL AtExit() called"<<endl;
  cout<<"  # objects allocated: "<<globjs.size()<<endl;
  it = globjs.begin();
  while(globjs.size() && it != globjs.end()) {
    GLObj *obj=*it;
    cout<<"[";
    switch(obj->type) {
    case GLOBJECT_TYPE_BUFFER: cout<<"buffer"; break;
    case GLOBJECT_TYPE_FRAMEBUFFER: cout<<"framebuffer"; break;
    case GLOBJECT_TYPE_PROGRAM: cout<<"program"; break;
    case GLOBJECT_TYPE_RENDERBUFFER: cout<<"renderbuffer"; break;
    case GLOBJECT_TYPE_SHADER: cout<<"shader"; break;
    case GLOBJECT_TYPE_TEXTURE: cout<<"texture"; break;
    };
    cout<<": "<<obj->obj<<"] ";
    ++it;
  }
  cout<<endl;
  #endif

  it = globjs.begin();
  while(globjs.size() && it != globjs.end()) {
    GLObj *globj=*it;
    GLuint obj=globj->obj;

    switch(globj->type) {
    case GLOBJECT_TYPE_PROGRAM:
      #ifdef LOGGING
      cout<<"  Destroying GL program "<<obj<<endl;
      #endif
      glDeleteProgram(obj);
      break;
    case GLOBJECT_TYPE_BUFFER:
      #ifdef LOGGING
      cout<<"  Destroying GL buffer "<<obj<<endl;
      #endif
      glDeleteBuffers(1,&obj);
      break;
    case GLOBJECT_TYPE_FRAMEBUFFER:
      #ifdef LOGGING
      cout<<"  Destroying GL frame buffer "<<obj<<endl;
      #endif
      glDeleteFramebuffers(1,&obj);
      break;
    case GLOBJECT_TYPE_RENDERBUFFER:
      #ifdef LOGGING
      cout<<"  Destroying GL render buffer "<<obj<<endl;
      #endif
      glDeleteRenderbuffers(1,&obj);
      break;
    case GLOBJECT_TYPE_SHADER:
      #ifdef LOGGING
      cout<<"  Destroying GL shader "<<obj<<endl;
      #endif
      glDeleteShader(obj);
      break;
    case GLOBJECT_TYPE_TEXTURE:
      #ifdef LOGGING
      cout<<"  Destroying GL texture "<<obj<<endl;
      #endif
      glDeleteTextures(1,&obj);
      break;
    default:
      #ifdef LOGGING
      cout<<"  Unknown object "<<obj<<endl;
      #endif
      break;
    }
    delete globj;
    ++it;
  }

  globjs.clear();
} */
