Blob Blame History Raw
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WebGLContextUtils.h"
#include "WebGLContext.h"

#include "GLContext.h"
#include "jsapi.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/Sprintf.h"
#include "nsIDOMEvent.h"
#include "nsIScriptSecurityManager.h"
#include "nsIVariant.h"
#include "nsPrintfCString.h"
#include "nsServiceManagerUtils.h"
#include <stdarg.h>
#include "WebGLBuffer.h"
#include "WebGLExtensions.h"
#include "WebGLFramebuffer.h"
#include "WebGLProgram.h"
#include "WebGLTexture.h"
#include "WebGLVertexArray.h"

namespace mozilla {

TexTarget TexImageTargetToTexTarget(TexImageTarget texImageTarget) {
  switch (texImageTarget.get()) {
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
      return LOCAL_GL_TEXTURE_CUBE_MAP;

    default:
      return texImageTarget.get();
  }
}

JS::Value StringValue(JSContext* cx, const char* chars, ErrorResult& rv) {
  JSString* str = JS_NewStringCopyZ(cx, chars);
  if (!str) {
    rv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return JS::NullValue();
  }

  return JS::StringValue(str);
}

void WebGLContext::GenerateWarning(const char* fmt, ...) const {
  va_list ap;
  va_start(ap, fmt);

  GenerateWarning(fmt, ap);

  va_end(ap);
}

void WebGLContext::GenerateWarning(const char* fmt, va_list ap) const {
  if (!ShouldGenerateWarnings()) return;

  mAlreadyGeneratedWarnings++;

  char buf[1024];
  VsprintfLiteral(buf, fmt, ap);

  // no need to print to stderr, as JS_ReportWarning takes care of this for us.

  if (!mCanvasElement) {
    return;
  }

  dom::AutoJSAPI api;
  if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject())) {
    return;
  }

  JSContext* cx = api.cx();
  JS_ReportWarningASCII(cx, "WebGL warning: %s", buf);
  if (!ShouldGenerateWarnings()) {
    JS_ReportWarningASCII(cx,
                          "WebGL: No further warnings will be reported for"
                          " this WebGL context."
                          " (already reported %d warnings)",
                          mAlreadyGeneratedWarnings);
  }
}

bool WebGLContext::ShouldGenerateWarnings() const {
  if (mMaxWarnings == -1) return true;

  return mAlreadyGeneratedWarnings < mMaxWarnings;
}

void WebGLContext::GeneratePerfWarning(const char* fmt, ...) const {
  if (!ShouldGeneratePerfWarnings()) return;

  if (!mCanvasElement) return;

  dom::AutoJSAPI api;
  if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject())) return;
  JSContext* cx = api.cx();

  ////

  va_list ap;
  va_start(ap, fmt);

  char buf[1024];
  VsprintfLiteral(buf, fmt, ap);

  va_end(ap);

  ////

  JS_ReportWarningASCII(cx, "WebGL perf warning: %s", buf);
  mNumPerfWarnings++;

  if (!ShouldGeneratePerfWarnings()) {
    JS_ReportWarningASCII(
        cx,
        "WebGL: After reporting %u, no further perf warnings will"
        " be reported for this WebGL context.",
        uint32_t(mNumPerfWarnings));
  }
}

void WebGLContext::SynthesizeGLError(GLenum err) const {
  /* ES2 section 2.5 "GL Errors" states that implementations can have
   * multiple 'flags', as errors might be caught in different parts of
   * a distributed implementation.
   * We're signing up as a distributed implementation here, with
   * separate flags for WebGL and the underlying GLContext.
   */
  if (!mWebGLError) mWebGLError = err;
}

void WebGLContext::SynthesizeGLError(GLenum err, const char* fmt, ...) const {
  va_list va;
  va_start(va, fmt);
  GenerateWarning(fmt, va);
  va_end(va);

  return SynthesizeGLError(err);
}

void WebGLContext::ErrorInvalidEnum(const char* fmt, ...) const {
  va_list va;
  va_start(va, fmt);
  GenerateWarning(fmt, va);
  va_end(va);

  return SynthesizeGLError(LOCAL_GL_INVALID_ENUM);
}

void WebGLContext::ErrorInvalidEnumInfo(const char* info,
                                        GLenum enumValue) const {
  nsCString name;
  EnumName(enumValue, &name);

  return ErrorInvalidEnum("%s: invalid enum value %s", info,
                          name.BeginReading());
}

void WebGLContext::ErrorInvalidEnumInfo(const char* info, const char* funcName,
                                        GLenum enumValue) const {
  nsCString name;
  EnumName(enumValue, &name);

  ErrorInvalidEnum("%s: %s: Invalid enum: 0x%04x (%s).", funcName, info,
                   enumValue, name.BeginReading());
}

void WebGLContext::ErrorInvalidOperation(const char* fmt, ...) const {
  va_list va;
  va_start(va, fmt);
  GenerateWarning(fmt, va);
  va_end(va);

  return SynthesizeGLError(LOCAL_GL_INVALID_OPERATION);
}

void WebGLContext::ErrorInvalidValue(const char* fmt, ...) const {
  va_list va;
  va_start(va, fmt);
  GenerateWarning(fmt, va);
  va_end(va);

  return SynthesizeGLError(LOCAL_GL_INVALID_VALUE);
}

void WebGLContext::ErrorInvalidFramebufferOperation(const char* fmt,
                                                    ...) const {
  va_list va;
  va_start(va, fmt);
  GenerateWarning(fmt, va);
  va_end(va);

  return SynthesizeGLError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION);
}

void WebGLContext::ErrorOutOfMemory(const char* fmt, ...) const {
  va_list va;
  va_start(va, fmt);
  GenerateWarning(fmt, va);
  va_end(va);

  return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY);
}

void WebGLContext::ErrorImplementationBug(const char* fmt, ...) const {
  const nsPrintfCString warning("Implementation bug, please file at %s! %s",
                                "https://bugzilla.mozilla.org/", fmt);

  va_list va;
  va_start(va, fmt);
  GenerateWarning(warning.BeginReading(), va);
  va_end(va);

  MOZ_ASSERT(false, "WebGLContext::ErrorImplementationBug");
  NS_ERROR("WebGLContext::ErrorImplementationBug");
  return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY);
}

/*static*/ const char* WebGLContext::ErrorName(GLenum error) {
  switch (error) {
    case LOCAL_GL_INVALID_ENUM:
      return "INVALID_ENUM";
    case LOCAL_GL_INVALID_OPERATION:
      return "INVALID_OPERATION";
    case LOCAL_GL_INVALID_VALUE:
      return "INVALID_VALUE";
    case LOCAL_GL_OUT_OF_MEMORY:
      return "OUT_OF_MEMORY";
    case LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION:
      return "INVALID_FRAMEBUFFER_OPERATION";
    case LOCAL_GL_NO_ERROR:
      return "NO_ERROR";
    default:
      MOZ_ASSERT(false);
      return "[unknown WebGL error]";
  }
}

// This version is fallible and will return nullptr if unrecognized.
static const char* GetEnumName(GLenum val) {
  switch (val) {
#define XX(x)        \
  case LOCAL_GL_##x: \
    return #x
    XX(NONE);
    XX(ALPHA);
    XX(ATC_RGB);
    XX(ATC_RGBA_EXPLICIT_ALPHA);
    XX(ATC_RGBA_INTERPOLATED_ALPHA);
    XX(COMPRESSED_RGBA_PVRTC_2BPPV1);
    XX(COMPRESSED_RGBA_PVRTC_4BPPV1);
    XX(COMPRESSED_RGBA_S3TC_DXT1_EXT);
    XX(COMPRESSED_RGBA_S3TC_DXT3_EXT);
    XX(COMPRESSED_RGBA_S3TC_DXT5_EXT);
    XX(COMPRESSED_RGB_PVRTC_2BPPV1);
    XX(COMPRESSED_RGB_PVRTC_4BPPV1);
    XX(COMPRESSED_RGB_S3TC_DXT1_EXT);
    XX(DEPTH_ATTACHMENT);
    XX(DEPTH_COMPONENT);
    XX(DEPTH_COMPONENT16);
    XX(DEPTH_COMPONENT32);
    XX(DEPTH_STENCIL);
    XX(DEPTH24_STENCIL8);
    XX(DRAW_FRAMEBUFFER);
    XX(ETC1_RGB8_OES);
    XX(FLOAT);
    XX(INT);
    XX(FRAMEBUFFER);
    XX(HALF_FLOAT);
    XX(LUMINANCE);
    XX(LUMINANCE_ALPHA);
    XX(READ_FRAMEBUFFER);
    XX(RGB);
    XX(RGB16F);
    XX(RGB32F);
    XX(RGBA);
    XX(RGBA16F);
    XX(RGBA32F);
    XX(SRGB);
    XX(SRGB_ALPHA);
    XX(TEXTURE_2D);
    XX(TEXTURE_3D);
    XX(TEXTURE_CUBE_MAP);
    XX(TEXTURE_CUBE_MAP_NEGATIVE_X);
    XX(TEXTURE_CUBE_MAP_NEGATIVE_Y);
    XX(TEXTURE_CUBE_MAP_NEGATIVE_Z);
    XX(TEXTURE_CUBE_MAP_POSITIVE_X);
    XX(TEXTURE_CUBE_MAP_POSITIVE_Y);
    XX(TEXTURE_CUBE_MAP_POSITIVE_Z);
    XX(UNSIGNED_BYTE);
    XX(UNSIGNED_INT);
    XX(UNSIGNED_INT_24_8);
    XX(UNSIGNED_SHORT);
    XX(UNSIGNED_SHORT_4_4_4_4);
    XX(UNSIGNED_SHORT_5_5_5_1);
    XX(UNSIGNED_SHORT_5_6_5);
    XX(READ_BUFFER);
    XX(UNPACK_ROW_LENGTH);
    XX(UNPACK_SKIP_ROWS);
    XX(UNPACK_SKIP_PIXELS);
    XX(PACK_ROW_LENGTH);
    XX(PACK_SKIP_ROWS);
    XX(PACK_SKIP_PIXELS);
    XX(COLOR);
    XX(DEPTH);
    XX(STENCIL);
    XX(RED);
    XX(RGB8);
    XX(RGBA8);
    XX(RGB10_A2);
    XX(TEXTURE_BINDING_3D);
    XX(UNPACK_SKIP_IMAGES);
    XX(UNPACK_IMAGE_HEIGHT);
    XX(TEXTURE_WRAP_R);
    XX(MAX_3D_TEXTURE_SIZE);
    XX(UNSIGNED_INT_2_10_10_10_REV);
    XX(MAX_ELEMENTS_VERTICES);
    XX(MAX_ELEMENTS_INDICES);
    XX(TEXTURE_MIN_LOD);
    XX(TEXTURE_MAX_LOD);
    XX(TEXTURE_BASE_LEVEL);
    XX(TEXTURE_MAX_LEVEL);
    XX(MIN);
    XX(MAX);
    XX(DEPTH_COMPONENT24);
    XX(MAX_TEXTURE_LOD_BIAS);
    XX(TEXTURE_COMPARE_MODE);
    XX(TEXTURE_COMPARE_FUNC);
    XX(CURRENT_QUERY);
    XX(QUERY_RESULT);
    XX(QUERY_RESULT_AVAILABLE);
    XX(STREAM_READ);
    XX(STREAM_COPY);
    XX(STATIC_READ);
    XX(STATIC_COPY);
    XX(DYNAMIC_READ);
    XX(DYNAMIC_COPY);
    XX(MAX_DRAW_BUFFERS);
    XX(DRAW_BUFFER0);
    XX(DRAW_BUFFER1);
    XX(DRAW_BUFFER2);
    XX(DRAW_BUFFER3);
    XX(DRAW_BUFFER4);
    XX(DRAW_BUFFER5);
    XX(DRAW_BUFFER6);
    XX(DRAW_BUFFER7);
    XX(DRAW_BUFFER8);
    XX(DRAW_BUFFER9);
    XX(DRAW_BUFFER10);
    XX(DRAW_BUFFER11);
    XX(DRAW_BUFFER12);
    XX(DRAW_BUFFER13);
    XX(DRAW_BUFFER14);
    XX(DRAW_BUFFER15);
    XX(MAX_FRAGMENT_UNIFORM_COMPONENTS);
    XX(MAX_VERTEX_UNIFORM_COMPONENTS);
    XX(SAMPLER_3D);
    XX(SAMPLER_2D_SHADOW);
    XX(FRAGMENT_SHADER_DERIVATIVE_HINT);
    XX(PIXEL_PACK_BUFFER);
    XX(PIXEL_UNPACK_BUFFER);
    XX(PIXEL_PACK_BUFFER_BINDING);
    XX(PIXEL_UNPACK_BUFFER_BINDING);
    XX(FLOAT_MAT2x3);
    XX(FLOAT_MAT2x4);
    XX(FLOAT_MAT3x2);
    XX(FLOAT_MAT3x4);
    XX(FLOAT_MAT4x2);
    XX(FLOAT_MAT4x3);
    XX(SRGB8);
    XX(SRGB8_ALPHA8);
    XX(COMPARE_REF_TO_TEXTURE);
    XX(VERTEX_ATTRIB_ARRAY_INTEGER);
    XX(MAX_ARRAY_TEXTURE_LAYERS);
    XX(MIN_PROGRAM_TEXEL_OFFSET);
    XX(MAX_PROGRAM_TEXEL_OFFSET);
    XX(MAX_VARYING_COMPONENTS);
    XX(TEXTURE_2D_ARRAY);
    XX(TEXTURE_BINDING_2D_ARRAY);
    XX(R11F_G11F_B10F);
    XX(UNSIGNED_INT_10F_11F_11F_REV);
    XX(RGB9_E5);
    XX(UNSIGNED_INT_5_9_9_9_REV);
    XX(TRANSFORM_FEEDBACK_BUFFER_MODE);
    XX(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS);
    XX(TRANSFORM_FEEDBACK_VARYINGS);
    XX(TRANSFORM_FEEDBACK_BUFFER_START);
    XX(TRANSFORM_FEEDBACK_BUFFER_SIZE);
    XX(TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
    XX(RASTERIZER_DISCARD);
    XX(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS);
    XX(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS);
    XX(INTERLEAVED_ATTRIBS);
    XX(SEPARATE_ATTRIBS);
    XX(TRANSFORM_FEEDBACK_BUFFER);
    XX(TRANSFORM_FEEDBACK_BUFFER_BINDING);
    XX(RGBA32UI);
    XX(RGB32UI);
    XX(RGBA16UI);
    XX(RGB16UI);
    XX(RGBA8UI);
    XX(RGB8UI);
    XX(RGBA32I);
    XX(RGB32I);
    XX(RGBA16I);
    XX(RGB16I);
    XX(RGBA8I);
    XX(RGB8I);
    XX(RED_INTEGER);
    XX(RGB_INTEGER);
    XX(RGBA_INTEGER);
    XX(SAMPLER_2D_ARRAY);
    XX(SAMPLER_2D_ARRAY_SHADOW);
    XX(SAMPLER_CUBE_SHADOW);
    XX(UNSIGNED_INT_VEC2);
    XX(UNSIGNED_INT_VEC3);
    XX(UNSIGNED_INT_VEC4);
    XX(INT_SAMPLER_2D);
    XX(INT_SAMPLER_3D);
    XX(INT_SAMPLER_CUBE);
    XX(INT_SAMPLER_2D_ARRAY);
    XX(UNSIGNED_INT_SAMPLER_2D);
    XX(UNSIGNED_INT_SAMPLER_3D);
    XX(UNSIGNED_INT_SAMPLER_CUBE);
    XX(UNSIGNED_INT_SAMPLER_2D_ARRAY);
    XX(DEPTH_COMPONENT32F);
    XX(DEPTH32F_STENCIL8);
    XX(FLOAT_32_UNSIGNED_INT_24_8_REV);
    XX(FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING);
    XX(FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE);
    XX(FRAMEBUFFER_ATTACHMENT_RED_SIZE);
    XX(FRAMEBUFFER_ATTACHMENT_GREEN_SIZE);
    XX(FRAMEBUFFER_ATTACHMENT_BLUE_SIZE);
    XX(FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE);
    XX(FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
    XX(FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
    XX(FRAMEBUFFER_DEFAULT);
    XX(DEPTH_STENCIL_ATTACHMENT);
    XX(UNSIGNED_NORMALIZED);
    XX(DRAW_FRAMEBUFFER_BINDING);
    XX(READ_FRAMEBUFFER_BINDING);
    XX(RENDERBUFFER_SAMPLES);
    XX(FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER);
    XX(MAX_COLOR_ATTACHMENTS);
    XX(COLOR_ATTACHMENT0);
    XX(COLOR_ATTACHMENT1);
    XX(COLOR_ATTACHMENT2);
    XX(COLOR_ATTACHMENT3);
    XX(COLOR_ATTACHMENT4);
    XX(COLOR_ATTACHMENT5);
    XX(COLOR_ATTACHMENT6);
    XX(COLOR_ATTACHMENT7);
    XX(COLOR_ATTACHMENT8);
    XX(COLOR_ATTACHMENT9);
    XX(COLOR_ATTACHMENT10);
    XX(COLOR_ATTACHMENT11);
    XX(COLOR_ATTACHMENT12);
    XX(COLOR_ATTACHMENT13);
    XX(COLOR_ATTACHMENT14);
    XX(COLOR_ATTACHMENT15);
    XX(FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
    XX(MAX_SAMPLES);
    XX(RG);
    XX(RG_INTEGER);
    XX(R8);
    XX(RG8);
    XX(R16F);
    XX(R32F);
    XX(RG16F);
    XX(RG32F);
    XX(R8I);
    XX(R8UI);
    XX(R16I);
    XX(R16UI);
    XX(R32I);
    XX(R32UI);
    XX(RG8I);
    XX(RG8UI);
    XX(RG16I);
    XX(RG16UI);
    XX(RG32I);
    XX(RG32UI);
    XX(VERTEX_ARRAY_BINDING);
    XX(R8_SNORM);
    XX(RG8_SNORM);
    XX(RGB8_SNORM);
    XX(RGBA8_SNORM);
    XX(SIGNED_NORMALIZED);
    XX(PRIMITIVE_RESTART_FIXED_INDEX);
    XX(COPY_READ_BUFFER);
    XX(COPY_WRITE_BUFFER);
    XX(UNIFORM_BUFFER);
    XX(UNIFORM_BUFFER_BINDING);
    XX(UNIFORM_BUFFER_START);
    XX(UNIFORM_BUFFER_SIZE);
    XX(MAX_VERTEX_UNIFORM_BLOCKS);
    XX(MAX_FRAGMENT_UNIFORM_BLOCKS);
    XX(MAX_COMBINED_UNIFORM_BLOCKS);
    XX(MAX_UNIFORM_BUFFER_BINDINGS);
    XX(MAX_UNIFORM_BLOCK_SIZE);
    XX(MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS);
    XX(MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS);
    XX(UNIFORM_BUFFER_OFFSET_ALIGNMENT);
    XX(ACTIVE_UNIFORM_BLOCKS);
    XX(UNIFORM_TYPE);
    XX(UNIFORM_SIZE);
    XX(UNIFORM_BLOCK_INDEX);
    XX(UNIFORM_OFFSET);
    XX(UNIFORM_ARRAY_STRIDE);
    XX(UNIFORM_MATRIX_STRIDE);
    XX(UNIFORM_IS_ROW_MAJOR);
    XX(UNIFORM_BLOCK_BINDING);
    XX(UNIFORM_BLOCK_DATA_SIZE);
    XX(UNIFORM_BLOCK_ACTIVE_UNIFORMS);
    XX(UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES);
    XX(UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER);
    XX(UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER);
    XX(MAX_VERTEX_OUTPUT_COMPONENTS);
    XX(MAX_FRAGMENT_INPUT_COMPONENTS);
    XX(MAX_SERVER_WAIT_TIMEOUT);
    XX(OBJECT_TYPE);
    XX(SYNC_CONDITION);
    XX(SYNC_STATUS);
    XX(SYNC_FLAGS);
    XX(SYNC_FENCE);
    XX(SYNC_GPU_COMMANDS_COMPLETE);
    XX(UNSIGNALED);
    XX(SIGNALED);
    XX(ALREADY_SIGNALED);
    XX(TIMEOUT_EXPIRED);
    XX(CONDITION_SATISFIED);
    XX(WAIT_FAILED);
    XX(VERTEX_ATTRIB_ARRAY_DIVISOR);
    XX(ANY_SAMPLES_PASSED);
    XX(ANY_SAMPLES_PASSED_CONSERVATIVE);
    XX(SAMPLER_BINDING);
    XX(RGB10_A2UI);
    XX(TEXTURE_SWIZZLE_R);
    XX(TEXTURE_SWIZZLE_G);
    XX(TEXTURE_SWIZZLE_B);
    XX(TEXTURE_SWIZZLE_A);
    XX(GREEN);
    XX(BLUE);
    XX(INT_2_10_10_10_REV);
    XX(TRANSFORM_FEEDBACK);
    XX(TRANSFORM_FEEDBACK_PAUSED);
    XX(TRANSFORM_FEEDBACK_ACTIVE);
    XX(TRANSFORM_FEEDBACK_BINDING);
    XX(COMPRESSED_R11_EAC);
    XX(COMPRESSED_SIGNED_R11_EAC);
    XX(COMPRESSED_RG11_EAC);
    XX(COMPRESSED_SIGNED_RG11_EAC);
    XX(COMPRESSED_RGB8_ETC2);
    XX(COMPRESSED_SRGB8_ETC2);
    XX(COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2);
    XX(COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2);
    XX(COMPRESSED_RGBA8_ETC2_EAC);
    XX(COMPRESSED_SRGB8_ALPHA8_ETC2_EAC);
    XX(TEXTURE_IMMUTABLE_FORMAT);
    XX(MAX_ELEMENT_INDEX);
    XX(NUM_SAMPLE_COUNTS);
    XX(TEXTURE_IMMUTABLE_LEVELS);
#undef XX
  }

  return nullptr;
}

/*static*/ void WebGLContext::EnumName(GLenum val, nsCString* out_name) {
  const char* name = GetEnumName(val);
  if (name) {
    *out_name = name;
    return;
  }

  *out_name = nsPrintfCString("<enum 0x%04x>", val);
}

void WebGLContext::ErrorInvalidEnumArg(const char* funcName,
                                       const char* argName, GLenum val) const {
  nsCString enumName;
  EnumName(val, &enumName);
  ErrorInvalidEnum("%s: Bad `%s`: %s", funcName, argName,
                   enumName.BeginReading());
}

bool IsCompressedTextureFormat(GLenum format) {
  switch (format) {
    case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
    case LOCAL_GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
    case LOCAL_GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
    case LOCAL_GL_ATC_RGB:
    case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
    case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
    case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
    case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
    case LOCAL_GL_ETC1_RGB8_OES:
    case LOCAL_GL_COMPRESSED_R11_EAC:
    case LOCAL_GL_COMPRESSED_SIGNED_R11_EAC:
    case LOCAL_GL_COMPRESSED_RG11_EAC:
    case LOCAL_GL_COMPRESSED_SIGNED_RG11_EAC:
    case LOCAL_GL_COMPRESSED_RGB8_ETC2:
    case LOCAL_GL_COMPRESSED_SRGB8_ETC2:
    case LOCAL_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
    case LOCAL_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
    case LOCAL_GL_COMPRESSED_RGBA8_ETC2_EAC:
    case LOCAL_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
      return true;
    default:
      return false;
  }
}

bool IsTextureFormatCompressed(TexInternalFormat format) {
  return IsCompressedTextureFormat(format.get());
}

GLenum WebGLContext::GetAndFlushUnderlyingGLErrors() const {
  // Get and clear GL error in ALL cases.
  GLenum error = gl->fGetError();

  // Only store in mUnderlyingGLError if is hasn't already recorded an
  // error.
  if (!mUnderlyingGLError) mUnderlyingGLError = error;

  return error;
}

#ifdef DEBUG
// For NaNs, etc.
static bool IsCacheCorrect(float cached, float actual) {
  if (IsNaN(cached)) {
    // GL is allowed to do anything it wants for NaNs, so if we're shadowing
    // a NaN, then whatever `actual` is might be correct.
    return true;
  }

  return cached == actual;
}

void AssertUintParamCorrect(gl::GLContext* gl, GLenum pname, GLuint shadow) {
  GLuint val = 0;
  gl->GetUIntegerv(pname, &val);
  if (val != shadow) {
    printf_stderr("Failed 0x%04x shadow: Cached 0x%x/%u, should be 0x%x/%u.\n",
                  pname, shadow, shadow, val, val);
    MOZ_ASSERT(false, "Bad cached value.");
  }
}

void AssertMaskedUintParamCorrect(gl::GLContext* gl, GLenum pname, GLuint mask,
                                  GLuint shadow) {
  GLuint val = 0;
  gl->GetUIntegerv(pname, &val);

  const GLuint valMasked = val & mask;
  const GLuint shadowMasked = shadow & mask;

  if (valMasked != shadowMasked) {
    printf_stderr("Failed 0x%04x shadow: Cached 0x%x/%u, should be 0x%x/%u.\n",
                  pname, shadowMasked, shadowMasked, valMasked, valMasked);
    MOZ_ASSERT(false, "Bad cached value.");
  }
}
#else
void AssertUintParamCorrect(gl::GLContext*, GLenum, GLuint) {}
#endif

void WebGLContext::AssertCachedBindings() const {
#ifdef DEBUG
  GetAndFlushUnderlyingGLErrors();

  if (IsWebGL2() ||
      IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object)) {
    GLuint bound = mBoundVertexArray ? mBoundVertexArray->GLName() : 0;
    AssertUintParamCorrect(gl, LOCAL_GL_VERTEX_ARRAY_BINDING, bound);
  }

  GLint stencilBits = 0;
  if (GetStencilBits(&stencilBits)) {  // Depends on current draw framebuffer.
    const GLuint stencilRefMask = (1 << stencilBits) - 1;

    AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_REF, stencilRefMask,
                                 mStencilRefFront);
    AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_REF, stencilRefMask,
                                 mStencilRefBack);
  }

  // Program
  GLuint bound = mCurrentProgram ? mCurrentProgram->mGLName : 0;
  AssertUintParamCorrect(gl, LOCAL_GL_CURRENT_PROGRAM, bound);

  // Textures
  GLenum activeTexture = mActiveTexture + LOCAL_GL_TEXTURE0;
  AssertUintParamCorrect(gl, LOCAL_GL_ACTIVE_TEXTURE, activeTexture);

  WebGLTexture* curTex = ActiveBoundTextureForTarget(LOCAL_GL_TEXTURE_2D);
  bound = curTex ? curTex->mGLName : 0;
  AssertUintParamCorrect(gl, LOCAL_GL_TEXTURE_BINDING_2D, bound);

  curTex = ActiveBoundTextureForTarget(LOCAL_GL_TEXTURE_CUBE_MAP);
  bound = curTex ? curTex->mGLName : 0;
  AssertUintParamCorrect(gl, LOCAL_GL_TEXTURE_BINDING_CUBE_MAP, bound);

  // Buffers
  bound = mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0;
  AssertUintParamCorrect(gl, LOCAL_GL_ARRAY_BUFFER_BINDING, bound);

  MOZ_ASSERT(mBoundVertexArray);
  WebGLBuffer* curBuff = mBoundVertexArray->mElementArrayBuffer;
  bound = curBuff ? curBuff->mGLName : 0;
  AssertUintParamCorrect(gl, LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING, bound);

  MOZ_ASSERT(!GetAndFlushUnderlyingGLErrors());
#endif

  // We do not check the renderbuffer binding, because we never rely on it
  // matching.
}

void WebGLContext::AssertCachedGlobalState() const {
#ifdef DEBUG
  GetAndFlushUnderlyingGLErrors();

  ////////////////

  // Draw state
  MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_DITHER) == mDitherEnabled);
  MOZ_ASSERT_IF(IsWebGL2(), gl->fIsEnabled(LOCAL_GL_RASTERIZER_DISCARD) ==
                                mRasterizerDiscardEnabled);
  MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST) == mScissorTestEnabled);

  // Cannot trivially check COLOR_CLEAR_VALUE, since in old GL versions glGet
  // may clamp based on whether the current framebuffer is floating-point or
  // not. This also means COLOR_CLEAR_VALUE save+restore is dangerous!

  realGLboolean depthWriteMask = 0;
  gl->fGetBooleanv(LOCAL_GL_DEPTH_WRITEMASK, &depthWriteMask);
  MOZ_ASSERT(depthWriteMask == mDepthWriteMask);

  GLfloat depthClearValue = 0.0f;
  gl->fGetFloatv(LOCAL_GL_DEPTH_CLEAR_VALUE, &depthClearValue);
  MOZ_ASSERT(IsCacheCorrect(mDepthClearValue, depthClearValue));

  const int maxStencilBits = 8;
  const GLuint maxStencilBitsMask = (1 << maxStencilBits) - 1;
  AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_CLEAR_VALUE,
                               maxStencilBitsMask, mStencilClearValue);

  // GLES 3.0.4, $4.1.4, p177:
  //   [...] the front and back stencil mask are both set to the value `2^s -
  //   1`, where `s` is greater than or equal to the number of bits in the
  //   deepest stencil buffer supported by the GL implementation.
  AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_VALUE_MASK,
                               maxStencilBitsMask, mStencilValueMaskFront);
  AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_VALUE_MASK,
                               maxStencilBitsMask, mStencilValueMaskBack);

  AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_WRITEMASK,
                               maxStencilBitsMask, mStencilWriteMaskFront);
  AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_WRITEMASK,
                               maxStencilBitsMask, mStencilWriteMaskBack);

  // Viewport
  GLint int4[4] = {0, 0, 0, 0};
  gl->fGetIntegerv(LOCAL_GL_VIEWPORT, int4);
  MOZ_ASSERT(int4[0] == mViewportX && int4[1] == mViewportY &&
             int4[2] == mViewportWidth && int4[3] == mViewportHeight);

  AssertUintParamCorrect(gl, LOCAL_GL_PACK_ALIGNMENT,
                         mPixelStore_PackAlignment);
  AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_ALIGNMENT,
                         mPixelStore_UnpackAlignment);

  if (IsWebGL2()) {
    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_IMAGE_HEIGHT,
                           mPixelStore_UnpackImageHeight);
    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_SKIP_IMAGES,
                           mPixelStore_UnpackSkipImages);
    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_ROW_LENGTH,
                           mPixelStore_UnpackRowLength);
    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_SKIP_ROWS,
                           mPixelStore_UnpackSkipRows);
    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_SKIP_PIXELS,
                           mPixelStore_UnpackSkipPixels);
    AssertUintParamCorrect(gl, LOCAL_GL_PACK_ROW_LENGTH,
                           mPixelStore_PackRowLength);
    AssertUintParamCorrect(gl, LOCAL_GL_PACK_SKIP_ROWS,
                           mPixelStore_PackSkipRows);
    AssertUintParamCorrect(gl, LOCAL_GL_PACK_SKIP_PIXELS,
                           mPixelStore_PackSkipPixels);
  }

  MOZ_ASSERT(!GetAndFlushUnderlyingGLErrors());
#endif
}

const char* InfoFrom(WebGLTexImageFunc func, WebGLTexDimensions dims) {
  switch (dims) {
    case WebGLTexDimensions::Tex2D:
      switch (func) {
        case WebGLTexImageFunc::TexImage:
          return "texImage2D";
        case WebGLTexImageFunc::TexSubImage:
          return "texSubImage2D";
        case WebGLTexImageFunc::CopyTexImage:
          return "copyTexImage2D";
        case WebGLTexImageFunc::CopyTexSubImage:
          return "copyTexSubImage2D";
        case WebGLTexImageFunc::CompTexImage:
          return "compressedTexImage2D";
        case WebGLTexImageFunc::CompTexSubImage:
          return "compressedTexSubImage2D";
        default:
          MOZ_CRASH("GFX: invalid 2D TexDimensions");
      }
    case WebGLTexDimensions::Tex3D:
      switch (func) {
        case WebGLTexImageFunc::TexImage:
          return "texImage3D";
        case WebGLTexImageFunc::TexSubImage:
          return "texSubImage3D";
        case WebGLTexImageFunc::CopyTexSubImage:
          return "copyTexSubImage3D";
        case WebGLTexImageFunc::CompTexSubImage:
          return "compressedTexSubImage3D";
        default:
          MOZ_CRASH("GFX: invalid 3D TexDimensions");
      }
    default:
      MOZ_CRASH("GFX: invalid TexDimensions");
  }
}

////

JS::Value StringValue(JSContext* cx, const nsAString& str, ErrorResult& er) {
  JSString* jsStr = JS_NewUCStringCopyN(cx, str.BeginReading(), str.Length());
  if (!jsStr) {
    er.Throw(NS_ERROR_OUT_OF_MEMORY);
    return JS::NullValue();
  }

  return JS::StringValue(jsStr);
}

}  // namespace mozilla