blob: 5ee321dd48e65bd1b36e64d371f32184cd702e81 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/webgl/WebGL2RenderingContextBase.h"
#include "bindings/modules/v8/WebGLAny.h"
#include "core/frame/ImageBitmap.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLVideoElement.h"
#include "core/html/ImageData.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "modules/webgl/WebGLActiveInfo.h"
#include "modules/webgl/WebGLBuffer.h"
#include "modules/webgl/WebGLFenceSync.h"
#include "modules/webgl/WebGLFramebuffer.h"
#include "modules/webgl/WebGLProgram.h"
#include "modules/webgl/WebGLQuery.h"
#include "modules/webgl/WebGLRenderbuffer.h"
#include "modules/webgl/WebGLSampler.h"
#include "modules/webgl/WebGLSync.h"
#include "modules/webgl/WebGLTexture.h"
#include "modules/webgl/WebGLTransformFeedback.h"
#include "modules/webgl/WebGLUniformLocation.h"
#include "modules/webgl/WebGLVertexArrayObject.h"
#include "public/platform/WebGraphicsContext3DProvider.h"
#include "wtf/CheckedNumeric.h"
#include "wtf/PtrUtil.h"
#include "wtf/text/WTFString.h"
#include <memory>
using WTF::String;
namespace blink {
namespace {
GLsync syncObjectOrZero(const WebGLSync* object) {
return object ? object->object() : nullptr;
}
bool validateSubSourceAndGetData(DOMArrayBufferView* view,
GLuint subOffset,
GLuint subLength,
void** outBaseAddress,
long long* outByteLength) {
// This is guaranteed to be non-null by DOM.
DCHECK(view);
size_t typeSize = view->typeSize();
DCHECK_GE(8u, typeSize);
long long byteLength = 0;
if (subLength) {
// type size is at most 8, so no overflow.
byteLength = subLength * typeSize;
}
long long byteOffset = 0;
if (subOffset) {
// type size is at most 8, so no overflow.
byteOffset = subOffset * typeSize;
}
CheckedNumeric<long long> total = byteOffset;
total += byteLength;
if (!total.IsValid() || total.ValueOrDie() > view->byteLength()) {
return false;
}
if (!byteLength) {
byteLength = view->byteLength() - byteOffset;
}
uint8_t* data = static_cast<uint8_t*>(view->baseAddress());
data += byteOffset;
*outBaseAddress = data;
*outByteLength = byteLength;
return true;
}
} // namespace
// These enums are from manual pages for glTexStorage2D/glTexStorage3D.
const GLenum kSupportedInternalFormatsStorage[] = {
GL_R8,
GL_R8_SNORM,
GL_R16F,
GL_R32F,
GL_R8UI,
GL_R8I,
GL_R16UI,
GL_R16I,
GL_R32UI,
GL_R32I,
GL_RG8,
GL_RG8_SNORM,
GL_RG16F,
GL_RG32F,
GL_RG8UI,
GL_RG8I,
GL_RG16UI,
GL_RG16I,
GL_RG32UI,
GL_RG32I,
GL_RGB8,
GL_SRGB8,
GL_RGB565,
GL_RGB8_SNORM,
GL_R11F_G11F_B10F,
GL_RGB9_E5,
GL_RGB16F,
GL_RGB32F,
GL_RGB8UI,
GL_RGB8I,
GL_RGB16UI,
GL_RGB16I,
GL_RGB32UI,
GL_RGB32I,
GL_RGBA8,
GL_SRGB8_ALPHA8,
GL_RGBA8_SNORM,
GL_RGB5_A1,
GL_RGBA4,
GL_RGB10_A2,
GL_RGBA16F,
GL_RGBA32F,
GL_RGBA8UI,
GL_RGBA8I,
GL_RGB10_A2UI,
GL_RGBA16UI,
GL_RGBA16I,
GL_RGBA32UI,
GL_RGBA32I,
GL_DEPTH_COMPONENT16,
GL_DEPTH_COMPONENT24,
GL_DEPTH_COMPONENT32F,
GL_DEPTH24_STENCIL8,
GL_DEPTH32F_STENCIL8,
};
const GLenum kCompressedTextureFormatsETC2EAC[] = {
GL_COMPRESSED_R11_EAC,
GL_COMPRESSED_SIGNED_R11_EAC,
GL_COMPRESSED_RG11_EAC,
GL_COMPRESSED_SIGNED_RG11_EAC,
GL_COMPRESSED_RGB8_ETC2,
GL_COMPRESSED_SRGB8_ETC2,
GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
GL_COMPRESSED_RGBA8_ETC2_EAC,
GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
};
WebGL2RenderingContextBase::WebGL2RenderingContextBase(
HTMLCanvasElement* passedCanvas,
std::unique_ptr<WebGraphicsContext3DProvider> contextProvider,
const CanvasContextCreationAttributes& requestedAttributes)
: WebGLRenderingContextBase(passedCanvas,
std::move(contextProvider),
requestedAttributes,
2) {
m_supportedInternalFormatsStorage.insert(
kSupportedInternalFormatsStorage,
kSupportedInternalFormatsStorage +
WTF_ARRAY_LENGTH(kSupportedInternalFormatsStorage));
m_supportedInternalFormatsStorage.insert(
kCompressedTextureFormatsETC2EAC,
kCompressedTextureFormatsETC2EAC +
WTF_ARRAY_LENGTH(kCompressedTextureFormatsETC2EAC));
m_compressedTextureFormatsETC2EAC.insert(
kCompressedTextureFormatsETC2EAC,
kCompressedTextureFormatsETC2EAC +
WTF_ARRAY_LENGTH(kCompressedTextureFormatsETC2EAC));
m_compressedTextureFormats.append(
kCompressedTextureFormatsETC2EAC,
WTF_ARRAY_LENGTH(kCompressedTextureFormatsETC2EAC));
}
WebGL2RenderingContextBase::~WebGL2RenderingContextBase() {
m_readFramebufferBinding = nullptr;
m_boundCopyReadBuffer = nullptr;
m_boundCopyWriteBuffer = nullptr;
m_boundPixelPackBuffer = nullptr;
m_boundPixelUnpackBuffer = nullptr;
m_boundTransformFeedbackBuffer = nullptr;
m_boundUniformBuffer = nullptr;
m_currentBooleanOcclusionQuery = nullptr;
m_currentTransformFeedbackPrimitivesWrittenQuery = nullptr;
}
void WebGL2RenderingContextBase::initializeNewContext() {
ASSERT(!isContextLost());
ASSERT(drawingBuffer());
m_readFramebufferBinding = nullptr;
m_boundCopyReadBuffer = nullptr;
m_boundCopyWriteBuffer = nullptr;
m_boundPixelPackBuffer = nullptr;
m_boundPixelUnpackBuffer = nullptr;
m_boundTransformFeedbackBuffer = nullptr;
m_boundUniformBuffer = nullptr;
m_currentBooleanOcclusionQuery = nullptr;
m_currentTransformFeedbackPrimitivesWrittenQuery = nullptr;
GLint numCombinedTextureImageUnits = 0;
contextGL()->GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
&numCombinedTextureImageUnits);
m_samplerUnits.clear();
m_samplerUnits.resize(numCombinedTextureImageUnits);
m_maxTransformFeedbackSeparateAttribs = 0;
contextGL()->GetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
&m_maxTransformFeedbackSeparateAttribs);
m_boundIndexedTransformFeedbackBuffers.clear();
m_boundIndexedTransformFeedbackBuffers.resize(
m_maxTransformFeedbackSeparateAttribs);
GLint maxUniformBufferBindings = 0;
contextGL()->GetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
&maxUniformBufferBindings);
m_boundIndexedUniformBuffers.clear();
m_boundIndexedUniformBuffers.resize(maxUniformBufferBindings);
m_maxBoundUniformBufferIndex = 0;
m_packRowLength = 0;
m_packSkipPixels = 0;
m_packSkipRows = 0;
m_unpackRowLength = 0;
m_unpackImageHeight = 0;
m_unpackSkipPixels = 0;
m_unpackSkipRows = 0;
m_unpackSkipImages = 0;
WebGLRenderingContextBase::initializeNewContext();
}
void WebGL2RenderingContextBase::bufferData(GLenum target,
DOMArrayBufferView* srcData,
GLenum usage,
GLuint srcOffset,
GLuint length) {
if (isContextLost())
return;
void* subBaseAddress = nullptr;
long long subByteLength = 0;
if (!validateSubSourceAndGetData(srcData, srcOffset, length, &subBaseAddress,
&subByteLength)) {
synthesizeGLError(GL_INVALID_VALUE, "bufferData",
"srcOffset + length too large");
return;
}
bufferDataImpl(target, subByteLength, subBaseAddress, usage);
}
void WebGL2RenderingContextBase::bufferData(GLenum target,
long long size,
GLenum usage) {
WebGLRenderingContextBase::bufferData(target, size, usage);
}
void WebGL2RenderingContextBase::bufferData(GLenum target,
DOMArrayBuffer* data,
GLenum usage) {
WebGLRenderingContextBase::bufferData(target, data, usage);
}
void WebGL2RenderingContextBase::bufferData(GLenum target,
DOMArrayBufferView* data,
GLenum usage) {
WebGLRenderingContextBase::bufferData(target, data, usage);
}
void WebGL2RenderingContextBase::bufferSubData(GLenum target,
GLintptr dstByteOffset,
DOMArrayBufferView* srcData,
GLuint srcOffset,
GLuint length) {
if (isContextLost())
return;
void* subBaseAddress = nullptr;
long long subByteLength = 0;
if (!validateSubSourceAndGetData(srcData, srcOffset, length, &subBaseAddress,
&subByteLength)) {
synthesizeGLError(GL_INVALID_VALUE, "bufferSubData",
"srcOffset + length too large");
return;
}
bufferSubDataImpl(target, dstByteOffset, subByteLength, subBaseAddress);
}
void WebGL2RenderingContextBase::bufferSubData(GLenum target,
long long offset,
DOMArrayBuffer* data) {
WebGLRenderingContextBase::bufferSubData(target, offset, data);
}
void WebGL2RenderingContextBase::bufferSubData(
GLenum target,
long long offset,
const FlexibleArrayBufferView& data) {
WebGLRenderingContextBase::bufferSubData(target, offset, data);
}
void WebGL2RenderingContextBase::copyBufferSubData(GLenum readTarget,
GLenum writeTarget,
long long readOffset,
long long writeOffset,
long long size) {
if (isContextLost())
return;
if (!validateValueFitNonNegInt32("copyBufferSubData", "readOffset",
readOffset) ||
!validateValueFitNonNegInt32("copyBufferSubData", "writeOffset",
writeOffset) ||
!validateValueFitNonNegInt32("copyBufferSubData", "size", size)) {
return;
}
WebGLBuffer* readBuffer =
validateBufferDataTarget("copyBufferSubData", readTarget);
if (!readBuffer)
return;
WebGLBuffer* writeBuffer =
validateBufferDataTarget("copyBufferSubData", writeTarget);
if (!writeBuffer)
return;
if (readOffset + size > readBuffer->getSize() ||
writeOffset + size > writeBuffer->getSize()) {
synthesizeGLError(GL_INVALID_VALUE, "copyBufferSubData", "buffer overflow");
return;
}
if ((writeBuffer->getInitialTarget() == GL_ELEMENT_ARRAY_BUFFER &&
readBuffer->getInitialTarget() != GL_ELEMENT_ARRAY_BUFFER) ||
(writeBuffer->getInitialTarget() != GL_ELEMENT_ARRAY_BUFFER &&
readBuffer->getInitialTarget() == GL_ELEMENT_ARRAY_BUFFER)) {
synthesizeGLError(GL_INVALID_OPERATION, "copyBufferSubData",
"Cannot copy into an element buffer destination from a "
"non-element buffer source");
return;
}
if (writeBuffer->getInitialTarget() == 0)
writeBuffer->setInitialTarget(readBuffer->getInitialTarget());
contextGL()->CopyBufferSubData(
readTarget, writeTarget, static_cast<GLintptr>(readOffset),
static_cast<GLintptr>(writeOffset), static_cast<GLsizeiptr>(size));
}
void WebGL2RenderingContextBase::getBufferSubData(GLenum target,
long long srcByteOffset,
DOMArrayBufferView* dstData,
GLuint dstOffset,
GLuint length) {
const char* funcName = "getBufferSubData";
if (isContextLost())
return;
if (!validateValueFitNonNegInt32(funcName, "srcByteOffset", srcByteOffset)) {
return;
}
WebGLBuffer* buffer = validateBufferDataTarget(funcName, target);
if (!buffer)
return;
void* subBaseAddress = nullptr;
long long subByteLength = 0;
if (!validateSubSourceAndGetData(dstData, dstOffset, length, &subBaseAddress,
&subByteLength)) {
synthesizeGLError(GL_INVALID_VALUE, funcName, "buffer overflow");
return;
}
void* mappedData =
contextGL()->MapBufferRange(target, static_cast<GLintptr>(srcByteOffset),
subByteLength, GL_MAP_READ_BIT);
if (!mappedData)
return;
memcpy(subBaseAddress, mappedData, subByteLength);
contextGL()->UnmapBuffer(target);
}
void WebGL2RenderingContextBase::blitFramebuffer(GLint srcX0,
GLint srcY0,
GLint srcX1,
GLint srcY1,
GLint dstX0,
GLint dstY0,
GLint dstX1,
GLint dstY1,
GLbitfield mask,
GLenum filter) {
if (isContextLost())
return;
contextGL()->BlitFramebufferCHROMIUM(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
dstX1, dstY1, mask, filter);
}
bool WebGL2RenderingContextBase::validateTexFuncLayer(const char* functionName,
GLenum texTarget,
GLint layer) {
if (layer < 0) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "layer out of range");
return false;
}
switch (texTarget) {
case GL_TEXTURE_3D:
if (layer > m_max3DTextureSize - 1) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "layer out of range");
return false;
}
break;
case GL_TEXTURE_2D_ARRAY:
if (layer > m_maxArrayTextureLayers - 1) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "layer out of range");
return false;
}
break;
default:
ASSERT_NOT_REACHED();
return false;
}
return true;
}
void WebGL2RenderingContextBase::framebufferTextureLayer(GLenum target,
GLenum attachment,
WebGLTexture* texture,
GLint level,
GLint layer) {
if (isContextLost() ||
!validateFramebufferFuncParameters("framebufferTextureLayer", target,
attachment))
return;
if (texture && !texture->validate(contextGroup(), this)) {
synthesizeGLError(GL_INVALID_VALUE, "framebufferTextureLayer",
"no texture or texture not from this context");
return;
}
GLenum textarget = texture ? texture->getTarget() : 0;
if (texture) {
if (textarget != GL_TEXTURE_3D && textarget != GL_TEXTURE_2D_ARRAY) {
synthesizeGLError(GL_INVALID_OPERATION, "framebufferTextureLayer",
"invalid texture type");
return;
}
if (!validateTexFuncLayer("framebufferTextureLayer", textarget, layer))
return;
if (!validateTexFuncLevel("framebufferTextureLayer", textarget, level))
return;
}
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
if (!framebufferBinding || !framebufferBinding->object()) {
synthesizeGLError(GL_INVALID_OPERATION, "framebufferTextureLayer",
"no framebuffer bound");
return;
}
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
contextGL()->FramebufferTextureLayer(target, GL_DEPTH_ATTACHMENT,
objectOrZero(texture), level, layer);
contextGL()->FramebufferTextureLayer(target, GL_STENCIL_ATTACHMENT,
objectOrZero(texture), level, layer);
} else {
contextGL()->FramebufferTextureLayer(target, attachment,
objectOrZero(texture), level, layer);
}
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
// On ES3, DEPTH_STENCIL_ATTACHMENT is like an alias for DEPTH_ATTACHMENT +
// STENCIL_ATTACHMENT. We divide it here so in WebGLFramebuffer, we don't
// have to handle DEPTH_STENCIL_ATTACHMENT in WebGL 2.
framebufferBinding->setAttachmentForBoundFramebuffer(
target, GL_DEPTH_ATTACHMENT, textarget, texture, level, layer);
framebufferBinding->setAttachmentForBoundFramebuffer(
target, GL_STENCIL_ATTACHMENT, textarget, texture, level, layer);
} else {
framebufferBinding->setAttachmentForBoundFramebuffer(
target, attachment, textarget, texture, level, layer);
}
applyStencilTest();
}
ScriptValue WebGL2RenderingContextBase::getInternalformatParameter(
ScriptState* scriptState,
GLenum target,
GLenum internalformat,
GLenum pname) {
if (isContextLost())
return ScriptValue::createNull(scriptState);
if (target != GL_RENDERBUFFER) {
synthesizeGLError(GL_INVALID_ENUM, "getInternalformatParameter",
"invalid target");
return ScriptValue::createNull(scriptState);
}
switch (internalformat) {
// Renderbuffer doesn't support unsized internal formats,
// though GL_RGB and GL_RGBA are color-renderable.
case GL_RGB:
case GL_RGBA:
// Multisampling is not supported for signed and unsigned integer internal
// formats.
case GL_R8UI:
case GL_R8I:
case GL_R16UI:
case GL_R16I:
case GL_R32UI:
case GL_R32I:
case GL_RG8UI:
case GL_RG8I:
case GL_RG16UI:
case GL_RG16I:
case GL_RG32UI:
case GL_RG32I:
case GL_RGBA8UI:
case GL_RGBA8I:
case GL_RGB10_A2UI:
case GL_RGBA16UI:
case GL_RGBA16I:
case GL_RGBA32UI:
case GL_RGBA32I:
return WebGLAny(scriptState, DOMInt32Array::create(0));
case GL_R8:
case GL_RG8:
case GL_RGB8:
case GL_RGB565:
case GL_RGBA8:
case GL_SRGB8_ALPHA8:
case GL_RGB5_A1:
case GL_RGBA4:
case GL_RGB10_A2:
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT24:
case GL_DEPTH_COMPONENT32F:
case GL_DEPTH24_STENCIL8:
case GL_DEPTH32F_STENCIL8:
case GL_STENCIL_INDEX8:
break;
case GL_R16F:
case GL_RG16F:
case GL_RGBA16F:
case GL_R32F:
case GL_RG32F:
case GL_RGBA32F:
case GL_R11F_G11F_B10F:
if (!extensionEnabled(EXTColorBufferFloatName)) {
synthesizeGLError(GL_INVALID_ENUM, "getInternalformatParameter",
"invalid internalformat when EXT_color_buffer_float "
"is not enabled");
return ScriptValue::createNull(scriptState);
}
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "getInternalformatParameter",
"invalid internalformat");
return ScriptValue::createNull(scriptState);
}
switch (pname) {
case GL_SAMPLES: {
std::unique_ptr<GLint[]> values;
GLint length = -1;
contextGL()->GetInternalformativ(target, internalformat,
GL_NUM_SAMPLE_COUNTS, 1, &length);
if (length <= 0)
return WebGLAny(scriptState, DOMInt32Array::create(0));
values = wrapArrayUnique(new GLint[length]);
for (GLint ii = 0; ii < length; ++ii)
values[ii] = 0;
contextGL()->GetInternalformativ(target, internalformat, GL_SAMPLES,
length, values.get());
return WebGLAny(scriptState, DOMInt32Array::create(values.get(), length));
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getInternalformatParameter",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
bool WebGL2RenderingContextBase::checkAndTranslateAttachments(
const char* functionName,
GLenum target,
Vector<GLenum>& attachments) {
if (!validateFramebufferTarget(target)) {
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return false;
}
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
ASSERT(framebufferBinding || drawingBuffer());
if (!framebufferBinding) {
// For the default framebuffer, translate GL_COLOR/GL_DEPTH/GL_STENCIL.
// The default framebuffer of WebGL is not fb 0, it is an internal fbo.
for (size_t i = 0; i < attachments.size(); ++i) {
switch (attachments[i]) {
case GL_COLOR:
attachments[i] = GL_COLOR_ATTACHMENT0;
break;
case GL_DEPTH:
attachments[i] = GL_DEPTH_ATTACHMENT;
break;
case GL_STENCIL:
attachments[i] = GL_STENCIL_ATTACHMENT;
break;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName,
"invalid attachment");
return false;
}
}
}
return true;
}
bool WebGL2RenderingContextBase::canUseTexImageByGPU(
TexImageFunctionID functionID,
GLint internalformat,
GLenum type) {
switch (internalformat) {
case GL_RGB565:
case GL_RGBA4:
case GL_RGB5_A1:
// FIXME: ES3 limitation that CopyTexImage with sized internalformat,
// component sizes have to match the source color format.
return false;
default:
break;
}
return WebGLRenderingContextBase::canUseTexImageByGPU(functionID,
internalformat, type);
}
void WebGL2RenderingContextBase::invalidateFramebuffer(
GLenum target,
const Vector<GLenum>& attachments) {
if (isContextLost())
return;
Vector<GLenum> translatedAttachments = attachments;
if (!checkAndTranslateAttachments("invalidateFramebuffer", target,
translatedAttachments))
return;
contextGL()->InvalidateFramebuffer(target, translatedAttachments.size(),
translatedAttachments.data());
}
void WebGL2RenderingContextBase::invalidateSubFramebuffer(
GLenum target,
const Vector<GLenum>& attachments,
GLint x,
GLint y,
GLsizei width,
GLsizei height) {
if (isContextLost())
return;
Vector<GLenum> translatedAttachments = attachments;
if (!checkAndTranslateAttachments("invalidateSubFramebuffer", target,
translatedAttachments))
return;
contextGL()->InvalidateSubFramebuffer(target, translatedAttachments.size(),
translatedAttachments.data(), x, y,
width, height);
}
void WebGL2RenderingContextBase::readBuffer(GLenum mode) {
if (isContextLost())
return;
switch (mode) {
case GL_BACK:
case GL_NONE:
case GL_COLOR_ATTACHMENT0:
break;
default:
if (mode > GL_COLOR_ATTACHMENT0 &&
mode <
static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + maxColorAttachments()))
break;
synthesizeGLError(GL_INVALID_ENUM, "readBuffer", "invalid read buffer");
return;
}
WebGLFramebuffer* readFramebufferBinding =
getFramebufferBinding(GL_READ_FRAMEBUFFER);
if (!readFramebufferBinding) {
ASSERT(drawingBuffer());
if (mode != GL_BACK && mode != GL_NONE) {
synthesizeGLError(GL_INVALID_OPERATION, "readBuffer",
"invalid read buffer");
return;
}
m_readBufferOfDefaultFramebuffer = mode;
// translate GL_BACK to GL_COLOR_ATTACHMENT0, because the default
// framebuffer for WebGL is not fb 0, it is an internal fbo.
if (mode == GL_BACK)
mode = GL_COLOR_ATTACHMENT0;
} else {
if (mode == GL_BACK) {
synthesizeGLError(GL_INVALID_OPERATION, "readBuffer",
"invalid read buffer");
return;
}
readFramebufferBinding->readBuffer(mode);
}
contextGL()->ReadBuffer(mode);
}
void WebGL2RenderingContextBase::pixelStorei(GLenum pname, GLint param) {
if (isContextLost())
return;
if (param < 0) {
synthesizeGLError(GL_INVALID_VALUE, "pixelStorei", "negative value");
return;
}
switch (pname) {
case GL_PACK_ROW_LENGTH:
m_packRowLength = param;
break;
case GL_PACK_SKIP_PIXELS:
m_packSkipPixels = param;
break;
case GL_PACK_SKIP_ROWS:
m_packSkipRows = param;
break;
case GL_UNPACK_ROW_LENGTH:
m_unpackRowLength = param;
break;
case GL_UNPACK_IMAGE_HEIGHT:
m_unpackImageHeight = param;
break;
case GL_UNPACK_SKIP_PIXELS:
m_unpackSkipPixels = param;
break;
case GL_UNPACK_SKIP_ROWS:
m_unpackSkipRows = param;
break;
case GL_UNPACK_SKIP_IMAGES:
m_unpackSkipImages = param;
break;
default:
WebGLRenderingContextBase::pixelStorei(pname, param);
return;
}
contextGL()->PixelStorei(pname, param);
}
void WebGL2RenderingContextBase::readPixels(GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
DOMArrayBufferView* pixels) {
if (isContextLost())
return;
if (m_boundPixelPackBuffer.get()) {
synthesizeGLError(GL_INVALID_OPERATION, "readPixels",
"PIXEL_PACK buffer should not be bound");
return;
}
WebGLRenderingContextBase::readPixels(x, y, width, height, format, type,
pixels);
}
void WebGL2RenderingContextBase::readPixels(GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
long long offset) {
if (isContextLost())
return;
// Due to WebGL's same-origin restrictions, it is not possible to
// taint the origin using the WebGL API.
ASSERT(canvas()->originClean());
if (!validateValueFitNonNegInt32("readPixels", "offset", offset))
return;
WebGLBuffer* buffer = m_boundPixelPackBuffer.get();
if (!buffer) {
synthesizeGLError(GL_INVALID_OPERATION, "readPixels",
"no PIXEL_PACK buffer bound");
return;
}
const char* reason = "framebuffer incomplete";
WebGLFramebuffer* framebuffer = getReadFramebufferBinding();
if (framebuffer &&
framebuffer->checkDepthStencilStatus(&reason) !=
GL_FRAMEBUFFER_COMPLETE) {
synthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, "readPixels", reason);
return;
}
long long size = buffer->getSize() - offset;
// If size is negative, or size is not large enough to store pixels, those
// cases are handled by validateReadPixelsFuncParameters to generate
// INVALID_OPERATION.
if (!validateReadPixelsFuncParameters(width, height, format, type, nullptr,
size))
return;
clearIfComposited();
{
ScopedDrawingBufferBinder binder(drawingBuffer(), framebuffer);
contextGL()->ReadPixels(x, y, width, height, format, type,
reinterpret_cast<void*>(offset));
}
}
void WebGL2RenderingContextBase::renderbufferStorageHelper(
GLenum target,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height,
const char* functionName) {
if (!samples) {
contextGL()->RenderbufferStorage(target, internalformat, width, height);
} else {
GLint maxNumberOfSamples = 0;
contextGL()->GetInternalformativ(target, internalformat, GL_SAMPLES, 1,
&maxNumberOfSamples);
if (samples > maxNumberOfSamples) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"samples out of range");
return;
}
contextGL()->RenderbufferStorageMultisampleCHROMIUM(
target, samples, internalformat, width, height);
}
}
void WebGL2RenderingContextBase::renderbufferStorageImpl(
GLenum target,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height,
const char* functionName) {
switch (internalformat) {
case GL_R8UI:
case GL_R8I:
case GL_R16UI:
case GL_R16I:
case GL_R32UI:
case GL_R32I:
case GL_RG8UI:
case GL_RG8I:
case GL_RG16UI:
case GL_RG16I:
case GL_RG32UI:
case GL_RG32I:
case GL_RGBA8UI:
case GL_RGBA8I:
case GL_RGB10_A2UI:
case GL_RGBA16UI:
case GL_RGBA16I:
case GL_RGBA32UI:
case GL_RGBA32I:
if (samples > 0) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"for integer formats, samples > 0");
return;
}
case GL_R8:
case GL_RG8:
case GL_RGB8:
case GL_RGB565:
case GL_RGBA8:
case GL_SRGB8_ALPHA8:
case GL_RGB5_A1:
case GL_RGBA4:
case GL_RGB10_A2:
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT24:
case GL_DEPTH_COMPONENT32F:
case GL_DEPTH24_STENCIL8:
case GL_DEPTH32F_STENCIL8:
case GL_STENCIL_INDEX8:
renderbufferStorageHelper(target, samples, internalformat, width, height,
functionName);
break;
case GL_DEPTH_STENCIL:
// To be WebGL 1 backward compatible.
if (samples > 0) {
synthesizeGLError(GL_INVALID_ENUM, functionName,
"invalid internalformat");
return;
}
renderbufferStorageHelper(target, 0, GL_DEPTH24_STENCIL8, width, height,
functionName);
break;
case GL_R16F:
case GL_RG16F:
case GL_RGBA16F:
case GL_R32F:
case GL_RG32F:
case GL_RGBA32F:
case GL_R11F_G11F_B10F:
if (!extensionEnabled(EXTColorBufferFloatName)) {
synthesizeGLError(GL_INVALID_ENUM, functionName,
"EXT_color_buffer_float not enabled");
return;
}
renderbufferStorageHelper(target, samples, internalformat, width, height,
functionName);
break;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName,
"invalid internalformat");
return;
}
m_renderbufferBinding->setInternalFormat(internalformat);
m_renderbufferBinding->setSize(width, height);
}
void WebGL2RenderingContextBase::renderbufferStorageMultisample(
GLenum target,
GLsizei samples,
GLenum internalformat,
GLsizei width,
GLsizei height) {
const char* functionName = "renderbufferStorageMultisample";
if (isContextLost())
return;
if (target != GL_RENDERBUFFER) {
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return;
}
if (!m_renderbufferBinding || !m_renderbufferBinding->object()) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"no bound renderbuffer");
return;
}
if (!validateSize("renderbufferStorage", width, height))
return;
if (samples < 0) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "samples < 0");
return;
}
renderbufferStorageImpl(target, samples, internalformat, width, height,
functionName);
applyStencilTest();
}
void WebGL2RenderingContextBase::resetUnpackParameters() {
WebGLRenderingContextBase::resetUnpackParameters();
if (!m_unpackRowLength)
contextGL()->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (!m_unpackImageHeight)
contextGL()->PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
if (!m_unpackSkipPixels)
contextGL()->PixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
if (!m_unpackSkipRows)
contextGL()->PixelStorei(GL_UNPACK_SKIP_ROWS, 0);
if (!m_unpackSkipImages)
contextGL()->PixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
}
void WebGL2RenderingContextBase::restoreUnpackParameters() {
WebGLRenderingContextBase::restoreUnpackParameters();
if (!m_unpackRowLength)
contextGL()->PixelStorei(GL_UNPACK_ROW_LENGTH, m_unpackRowLength);
if (!m_unpackImageHeight)
contextGL()->PixelStorei(GL_UNPACK_IMAGE_HEIGHT, m_unpackImageHeight);
if (!m_unpackSkipPixels)
contextGL()->PixelStorei(GL_UNPACK_SKIP_PIXELS, m_unpackSkipPixels);
if (!m_unpackSkipRows)
contextGL()->PixelStorei(GL_UNPACK_SKIP_ROWS, m_unpackSkipRows);
if (!m_unpackSkipImages)
contextGL()->PixelStorei(GL_UNPACK_SKIP_IMAGES, m_unpackSkipImages);
}
/* Texture objects */
bool WebGL2RenderingContextBase::validateTexStorage(
const char* functionName,
GLenum target,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
TexStorageType functionType) {
if (functionType == TexStorageType2D) {
if (target != GL_TEXTURE_2D && target != GL_TEXTURE_CUBE_MAP) {
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid 2D target");
return false;
}
} else {
if (target != GL_TEXTURE_3D && target != GL_TEXTURE_2D_ARRAY) {
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid 3D target");
return false;
}
}
if (functionType == TexStorageType3D && target != GL_TEXTURE_2D_ARRAY &&
m_compressedTextureFormatsETC2EAC.find(internalformat) !=
m_compressedTextureFormatsETC2EAC.end()) {
synthesizeGLError(
GL_INVALID_OPERATION, functionName,
"target for ETC2/EAC internal formats must be TEXTURE_2D_ARRAY");
return false;
}
if (m_supportedInternalFormatsStorage.find(internalformat) ==
m_supportedInternalFormatsStorage.end() &&
(functionType == TexStorageType2D &&
!m_compressedTextureFormats.contains(internalformat))) {
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid internalformat");
return false;
}
if (width <= 0 || height <= 0 || depth <= 0) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "invalid dimensions");
return false;
}
if (levels <= 0) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "invalid levels");
return false;
}
if (target == GL_TEXTURE_3D) {
if (levels > log2(std::max(std::max(width, height), depth)) + 1) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "to many levels");
return false;
}
} else {
if (levels > log2(std::max(width, height)) + 1) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "to many levels");
return false;
}
}
return true;
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
GLintptr offset) {
if (isContextLost())
return;
if (!validateTexture2DBinding("texImage2D", target))
return;
if (!m_boundPixelUnpackBuffer) {
synthesizeGLError(GL_INVALID_OPERATION, "texImage2D",
"no bound PIXEL_UNPACK_BUFFER");
return;
}
if (!validateTexFunc("texImage2D", TexImage, SourceUnpackBuffer, target,
level, internalformat, width, height, 1, border, format,
type, 0, 0, 0))
return;
if (!validateValueFitNonNegInt32("texImage2D", "offset", offset))
return;
contextGL()->TexImage2D(
target, level, convertTexInternalFormat(internalformat, type), width,
height, border, format, type, reinterpret_cast<const void*>(offset));
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
GLintptr offset) {
if (isContextLost())
return;
if (!validateTexture2DBinding("texSubImage2D", target))
return;
if (!m_boundPixelUnpackBuffer) {
synthesizeGLError(GL_INVALID_OPERATION, "texSubImage2D",
"no bound PIXEL_UNPACK_BUFFER");
return;
}
if (!validateTexFunc("texSubImage2D", TexSubImage, SourceUnpackBuffer, target,
level, 0, width, height, 1, 0, format, type, xoffset,
yoffset, 0))
return;
if (!validateValueFitNonNegInt32("texSubImage2D", "offset", offset))
return;
contextGL()->TexSubImage2D(target, level, xoffset, yoffset, width, height,
format, type,
reinterpret_cast<const void*>(offset));
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
DOMArrayBufferView* data) {
WebGLRenderingContextBase::texImage2D(target, level, internalformat, width,
height, border, format, type, data);
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
DOMArrayBufferView* data,
GLuint srcOffset) {
texImageHelperDOMArrayBufferView(TexImage2D, target, level, internalformat,
width, height, 1, border, format, type, 0, 0,
0, data, NullNotReachable, srcOffset);
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
ImageData* imageData) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
ImageBitmap* imageBitMap,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLenum format,
GLenum type,
ImageData* imageData) {
WebGLRenderingContextBase::texImage2D(target, level, internalformat, format,
type, imageData);
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texImage2D(target, level, internalformat, format,
type, image, exceptionState);
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texImage2D(target, level, internalformat, format,
type, canvas, exceptionState);
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texImage2D(target, level, internalformat, format,
type, video, exceptionState);
}
void WebGL2RenderingContextBase::texImage2D(GLenum target,
GLint level,
GLint internalformat,
GLenum format,
GLenum type,
ImageBitmap* imageBitMap,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texImage2D(target, level, internalformat, format,
type, imageBitMap, exceptionState);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
DOMArrayBufferView* pixels) {
WebGLRenderingContextBase::texSubImage2D(target, level, xoffset, yoffset,
width, height, format, type, pixels);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
DOMArrayBufferView* pixels,
GLuint srcOffset) {
texImageHelperDOMArrayBufferView(TexSubImage2D, target, level, 0, width,
height, 1, 0, format, type, xoffset, yoffset,
0, pixels, NullNotReachable, srcOffset);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
ImageData* pixels) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemente.
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
ImageBitmap* bitmap,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLenum format,
GLenum type,
ImageData* pixels) {
WebGLRenderingContextBase::texSubImage2D(target, level, xoffset, yoffset,
format, type, pixels);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texSubImage2D(target, level, xoffset, yoffset,
format, type, image, exceptionState);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texSubImage2D(
target, level, xoffset, yoffset, format, type, canvas, exceptionState);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texSubImage2D(target, level, xoffset, yoffset,
format, type, video, exceptionState);
}
void WebGL2RenderingContextBase::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLenum format,
GLenum type,
ImageBitmap* bitmap,
ExceptionState& exceptionState) {
WebGLRenderingContextBase::texSubImage2D(
target, level, xoffset, yoffset, format, type, bitmap, exceptionState);
}
void WebGL2RenderingContextBase::texStorage2D(GLenum target,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height) {
if (isContextLost() ||
!validateTexStorage("texStorage2D", target, levels, internalformat, width,
height, 1, TexStorageType2D))
return;
contextGL()->TexStorage2DEXT(target, levels, internalformat, width, height);
}
void WebGL2RenderingContextBase::texStorage3D(GLenum target,
GLsizei levels,
GLenum internalformat,
GLsizei width,
GLsizei height,
GLsizei depth) {
if (isContextLost() ||
!validateTexStorage("texStorage3D", target, levels, internalformat, width,
height, depth, TexStorageType3D))
return;
contextGL()->TexStorage3D(target, levels, internalformat, width, height,
depth);
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
DOMArrayBufferView* pixels) {
texImageHelperDOMArrayBufferView(TexImage3D, target, level, internalformat,
width, height, depth, border, format, type,
0, 0, 0, pixels, NullAllowed, 0);
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
DOMArrayBufferView* pixels,
GLuint srcOffset) {
texImageHelperDOMArrayBufferView(
TexImage3D, target, level, internalformat, width, height, depth, border,
format, type, 0, 0, 0, pixels, NullNotReachable, srcOffset);
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
GLintptr offset) {
if (isContextLost())
return;
if (!validateTexture3DBinding("texImage3D", target))
return;
if (!m_boundPixelUnpackBuffer) {
synthesizeGLError(GL_INVALID_OPERATION, "texImage3D",
"no bound PIXEL_UNPACK_BUFFER");
return;
}
if (!validateTexFunc("texImage3D", TexImage, SourceUnpackBuffer, target,
level, internalformat, width, height, depth, border,
format, type, 0, 0, 0))
return;
if (!validateValueFitNonNegInt32("texImage3D", "offset", offset))
return;
contextGL()->TexImage3D(target, level,
convertTexInternalFormat(internalformat, type), width,
height, depth, border, format, type,
reinterpret_cast<const void*>(offset));
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
ImageData* imageData) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texImage3D(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
ImageBitmap* imageBitMap,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
DOMArrayBufferView* pixels,
GLuint srcOffset) {
texImageHelperDOMArrayBufferView(
TexSubImage3D, target, level, 0, width, height, depth, 0, format, type,
xoffset, yoffset, zoffset, pixels, NullNotReachable, srcOffset);
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
GLintptr offset) {
if (isContextLost())
return;
if (!validateTexture3DBinding("texSubImage3D", target))
return;
if (!m_boundPixelUnpackBuffer) {
synthesizeGLError(GL_INVALID_OPERATION, "texSubImage3D",
"no bound PIXEL_UNPACK_BUFFER");
return;
}
if (!validateTexFunc("texSubImage3D", TexSubImage, SourceUnpackBuffer, target,
level, 0, width, height, depth, 0, format, type, xoffset,
yoffset, zoffset))
return;
if (!validateValueFitNonNegInt32("texSubImage3D", "offset", offset))
return;
contextGL()->TexSubImage3D(target, level, xoffset, yoffset, zoffset, width,
height, depth, format, type,
reinterpret_cast<const void*>(offset));
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
ImageData* pixels) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
ImageBitmap* bitmap,
ExceptionState& exceptionState) {
// TODO(zmo): To be implemented.
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLenum format,
GLenum type,
ImageData* pixels) {
texImageHelperImageData(TexSubImage3D, target, level, 0, 0, format, type, 1,
xoffset, yoffset, zoffset, pixels);
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLenum format,
GLenum type,
HTMLImageElement* image,
ExceptionState& exceptionState) {
texImageHelperHTMLImageElement(TexSubImage3D, target, level, 0, format, type,
xoffset, yoffset, zoffset, image,
exceptionState);
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLenum format,
GLenum type,
HTMLCanvasElement* canvas,
ExceptionState& exceptionState) {
texImageHelperHTMLCanvasElement(TexSubImage3D, target, level, 0, format, type,
xoffset, yoffset, zoffset, canvas,
exceptionState);
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLenum format,
GLenum type,
HTMLVideoElement* video,
ExceptionState& exceptionState) {
texImageHelperHTMLVideoElement(TexSubImage3D, target, level, 0, format, type,
xoffset, yoffset, zoffset, video,
exceptionState);
}
void WebGL2RenderingContextBase::texSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLenum format,
GLenum type,
ImageBitmap* bitmap,
ExceptionState& exceptionState) {
texImageHelperImageBitmap(TexSubImage3D, target, level, 0, format, type,
xoffset, yoffset, zoffset, bitmap, exceptionState);
}
void WebGL2RenderingContextBase::copyTexSubImage3D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLint x,
GLint y,
GLsizei width,
GLsizei height) {
if (isContextLost())
return;
if (!validateTexture3DBinding("copyTexSubImage3D", target))
return;
WebGLFramebuffer* readFramebufferBinding = nullptr;
if (!validateReadBufferAndGetInfo("copyTexSubImage3D",
readFramebufferBinding))
return;
clearIfComposited();
ScopedDrawingBufferBinder binder(drawingBuffer(), readFramebufferBinding);
contextGL()->CopyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y,
width, height);
}
void WebGL2RenderingContextBase::compressedTexImage3D(
GLenum target,
GLint level,
GLenum internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
DOMArrayBufferView* data) {
if (isContextLost())
return;
if (!validateTexture3DBinding("compressedTexImage3D", target))
return;
if (!validateCompressedTexFormat("compressedTexImage3D", internalformat))
return;
contextGL()->CompressedTexImage3D(target, level, internalformat, width,
height, depth, border, data->byteLength(),
data->baseAddress());
}
void WebGL2RenderingContextBase::compressedTexSubImage3D(
GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
DOMArrayBufferView* data) {
if (isContextLost())
return;
if (!validateTexture3DBinding("compressedTexSubImage3D", target))
return;
if (!validateCompressedTexFormat("compressedTexSubImage3D", format))
return;
contextGL()->CompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset,
width, height, depth, format,
data->byteLength(), data->baseAddress());
}
GLint WebGL2RenderingContextBase::getFragDataLocation(WebGLProgram* program,
const String& name) {
if (isContextLost() || !validateWebGLObject("getFragDataLocation", program))
return -1;
return contextGL()->GetFragDataLocation(objectOrZero(program),
name.utf8().data());
}
void WebGL2RenderingContextBase::uniform1ui(
const WebGLUniformLocation* location,
GLuint v0) {
if (isContextLost() || !location)
return;
if (location->program() != m_currentProgram) {
synthesizeGLError(GL_INVALID_OPERATION, "uniform1ui",
"location not for current program");
return;
}
contextGL()->Uniform1ui(location->location(), v0);
}
void WebGL2RenderingContextBase::uniform2ui(
const WebGLUniformLocation* location,
GLuint v0,
GLuint v1) {
if (isContextLost() || !location)
return;
if (location->program() != m_currentProgram) {
synthesizeGLError(GL_INVALID_OPERATION, "uniform2ui",
"location not for current program");
return;
}
contextGL()->Uniform2ui(location->location(), v0, v1);
}
void WebGL2RenderingContextBase::uniform3ui(
const WebGLUniformLocation* location,
GLuint v0,
GLuint v1,
GLuint v2) {
if (isContextLost() || !location)
return;
if (location->program() != m_currentProgram) {
synthesizeGLError(GL_INVALID_OPERATION, "uniform3ui",
"location not for current program");
return;
}
contextGL()->Uniform3ui(location->location(), v0, v1, v2);
}
void WebGL2RenderingContextBase::uniform4ui(
const WebGLUniformLocation* location,
GLuint v0,
GLuint v1,
GLuint v2,
GLuint v3) {
if (isContextLost() || !location)
return;
if (location->program() != m_currentProgram) {
synthesizeGLError(GL_INVALID_OPERATION, "uniform4ui",
"location not for current program");
return;
}
contextGL()->Uniform4ui(location->location(), v0, v1, v2, v3);
}
void WebGL2RenderingContextBase::uniform1uiv(
const WebGLUniformLocation* location,
const FlexibleUint32ArrayView& v) {
if (isContextLost() ||
!validateUniformParameters<WTF::Uint32Array>("uniform1uiv", location, v,
1))
return;
contextGL()->Uniform1uiv(location->location(), v.length(),
v.dataMaybeOnStack());
}
void WebGL2RenderingContextBase::uniform1uiv(
const WebGLUniformLocation* location,
Vector<GLuint>& value) {
if (isContextLost() ||
!validateUniformParameters("uniform1uiv", location, value.data(),
value.size(), 1))
return;
contextGL()->Uniform1uiv(location->location(), value.size(), value.data());
}
void WebGL2RenderingContextBase::uniform2uiv(
const WebGLUniformLocation* location,
const FlexibleUint32ArrayView& v) {
if (isContextLost() ||
!validateUniformParameters<WTF::Uint32Array>("uniform2uiv", location, v,
2))
return;
contextGL()->Uniform2uiv(location->location(), v.length() >> 1,
v.dataMaybeOnStack());
}
void WebGL2RenderingContextBase::uniform2uiv(
const WebGLUniformLocation* location,
Vector<GLuint>& value) {
if (isContextLost() ||
!validateUniformParameters("uniform2uiv", location, value.data(),
value.size(), 2))
return;
contextGL()->Uniform2uiv(location->location(), value.size() / 2,
value.data());
}
void WebGL2RenderingContextBase::uniform3uiv(
const WebGLUniformLocation* location,
const FlexibleUint32ArrayView& v) {
if (isContextLost() ||
!validateUniformParameters<WTF::Uint32Array>("uniform3uiv", location, v,
3))
return;
contextGL()->Uniform3uiv(location->location(), v.length() / 3,
v.dataMaybeOnStack());
}
void WebGL2RenderingContextBase::uniform3uiv(
const WebGLUniformLocation* location,
Vector<GLuint>& value) {
if (isContextLost() ||
!validateUniformParameters("uniform3uiv", location, value.data(),
value.size(), 3))
return;
contextGL()->Uniform3uiv(location->location(), value.size() / 3,
value.data());
}
void WebGL2RenderingContextBase::uniform4uiv(
const WebGLUniformLocation* location,
const FlexibleUint32ArrayView& v) {
if (isContextLost() ||
!validateUniformParameters<WTF::Uint32Array>("uniform4uiv", location, v,
4))
return;
contextGL()->Uniform4uiv(location->location(), v.length() >> 2,
v.dataMaybeOnStack());
}
void WebGL2RenderingContextBase::uniform4uiv(
const WebGLUniformLocation* location,
Vector<GLuint>& value) {
if (isContextLost() ||
!validateUniformParameters("uniform4uiv", location, value.data(),
value.size(), 4))
return;
contextGL()->Uniform4uiv(location->location(), value.size() / 4,
value.data());
}
void WebGL2RenderingContextBase::uniformMatrix2x3fv(
const WebGLUniformLocation* location,
GLboolean transpose,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix2x3fv", location,
transpose, value, 6))
return;
contextGL()->UniformMatrix2x3fv(location->location(), value->length() / 6,
transpose, value->data());
}
void WebGL2RenderingContextBase::uniformMatrix2x3fv(
const WebGLUniformLocation* location,
GLboolean transpose,
Vector<GLfloat>& value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix2x3fv", location,
transpose, value.data(), value.size(),
6))
return;
contextGL()->UniformMatrix2x3fv(location->location(), value.size() / 6,
transpose, value.data());
}
void WebGL2RenderingContextBase::uniformMatrix3x2fv(
const WebGLUniformLocation* location,
GLboolean transpose,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix3x2fv", location,
transpose, value, 6))
return;
contextGL()->UniformMatrix3x2fv(location->location(), value->length() / 6,
transpose, value->data());
}
void WebGL2RenderingContextBase::uniformMatrix3x2fv(
const WebGLUniformLocation* location,
GLboolean transpose,
Vector<GLfloat>& value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix3x2fv", location,
transpose, value.data(), value.size(),
6))
return;
contextGL()->UniformMatrix3x2fv(location->location(), value.size() / 6,
transpose, value.data());
}
void WebGL2RenderingContextBase::uniformMatrix2x4fv(
const WebGLUniformLocation* location,
GLboolean transpose,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix2x4fv", location,
transpose, value, 8))
return;
contextGL()->UniformMatrix2x4fv(location->location(), value->length() / 8,
transpose, value->data());
}
void WebGL2RenderingContextBase::uniformMatrix2x4fv(
const WebGLUniformLocation* location,
GLboolean transpose,
Vector<GLfloat>& value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix2x4fv", location,
transpose, value.data(), value.size(),
8))
return;
contextGL()->UniformMatrix2x4fv(location->location(), value.size() / 8,
transpose, value.data());
}
void WebGL2RenderingContextBase::uniformMatrix4x2fv(
const WebGLUniformLocation* location,
GLboolean transpose,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix4x2fv", location,
transpose, value, 8))
return;
contextGL()->UniformMatrix4x2fv(location->location(), value->length() / 8,
transpose, value->data());
}
void WebGL2RenderingContextBase::uniformMatrix4x2fv(
const WebGLUniformLocation* location,
GLboolean transpose,
Vector<GLfloat>& value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix4x2fv", location,
transpose, value.data(), value.size(),
8))
return;
contextGL()->UniformMatrix4x2fv(location->location(), value.size() / 8,
transpose, value.data());
}
void WebGL2RenderingContextBase::uniformMatrix3x4fv(
const WebGLUniformLocation* location,
GLboolean transpose,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix3x4fv", location,
transpose, value, 12))
return;
contextGL()->UniformMatrix3x4fv(location->location(), value->length() / 12,
transpose, value->data());
}
void WebGL2RenderingContextBase::uniformMatrix3x4fv(
const WebGLUniformLocation* location,
GLboolean transpose,
Vector<GLfloat>& value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix3x4fv", location,
transpose, value.data(), value.size(),
12))
return;
contextGL()->UniformMatrix3x4fv(location->location(), value.size() / 12,
transpose, value.data());
}
void WebGL2RenderingContextBase::uniformMatrix4x3fv(
const WebGLUniformLocation* location,
GLboolean transpose,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix4x3fv", location,
transpose, value, 12))
return;
contextGL()->UniformMatrix4x3fv(location->location(), value->length() / 12,
transpose, value->data());
}
void WebGL2RenderingContextBase::uniformMatrix4x3fv(
const WebGLUniformLocation* location,
GLboolean transpose,
Vector<GLfloat>& value) {
if (isContextLost() ||
!validateUniformMatrixParameters("uniformMatrix4x3fv", location,
transpose, value.data(), value.size(),
12))
return;
contextGL()->UniformMatrix4x3fv(location->location(), value.size() / 12,
transpose, value.data());
}
void WebGL2RenderingContextBase::vertexAttribI4i(GLuint index,
GLint x,
GLint y,
GLint z,
GLint w) {
if (isContextLost())
return;
contextGL()->VertexAttribI4i(index, x, y, z, w);
setVertexAttribType(index, Int32ArrayType);
}
void WebGL2RenderingContextBase::vertexAttribI4iv(GLuint index,
const DOMInt32Array* v) {
if (isContextLost())
return;
if (!v || v->length() < 4) {
synthesizeGLError(GL_INVALID_VALUE, "vertexAttribI4iv", "invalid array");
return;
}
contextGL()->VertexAttribI4iv(index, v->data());
setVertexAttribType(index, Int32ArrayType);
}
void WebGL2RenderingContextBase::vertexAttribI4iv(GLuint index,
const Vector<GLint>& v) {
if (isContextLost())
return;
if (v.size() < 4) {
synthesizeGLError(GL_INVALID_VALUE, "vertexAttribI4iv", "invalid array");
return;
}
contextGL()->VertexAttribI4iv(index, v.data());
setVertexAttribType(index, Int32ArrayType);
}
void WebGL2RenderingContextBase::vertexAttribI4ui(GLuint index,
GLuint x,
GLuint y,
GLuint z,
GLuint w) {
if (isContextLost())
return;
contextGL()->VertexAttribI4ui(index, x, y, z, w);
setVertexAttribType(index, Uint32ArrayType);
}
void WebGL2RenderingContextBase::vertexAttribI4uiv(GLuint index,
const DOMUint32Array* v) {
if (isContextLost())
return;
if (!v || v->length() < 4) {
synthesizeGLError(GL_INVALID_VALUE, "vertexAttribI4uiv", "invalid array");
return;
}
contextGL()->VertexAttribI4uiv(index, v->data());
setVertexAttribType(index, Uint32ArrayType);
}
void WebGL2RenderingContextBase::vertexAttribI4uiv(GLuint index,
const Vector<GLuint>& v) {
if (isContextLost())
return;
if (v.size() < 4) {
synthesizeGLError(GL_INVALID_VALUE, "vertexAttribI4uiv", "invalid array");
return;
}
contextGL()->VertexAttribI4uiv(index, v.data());
setVertexAttribType(index, Uint32ArrayType);
}
void WebGL2RenderingContextBase::vertexAttribIPointer(GLuint index,
GLint size,
GLenum type,
GLsizei stride,
long long offset) {
if (isContextLost())
return;
if (index >= m_maxVertexAttribs) {
synthesizeGLError(GL_INVALID_VALUE, "vertexAttribIPointer",
"index out of range");
return;
}
if (!validateValueFitNonNegInt32("vertexAttribIPointer", "offset", offset))
return;
if (!m_boundArrayBuffer) {
synthesizeGLError(GL_INVALID_OPERATION, "vertexAttribIPointer",
"no bound ARRAY_BUFFER");
return;
}
m_boundVertexArrayObject->setArrayBufferForAttrib(index, m_boundArrayBuffer);
contextGL()->VertexAttribIPointer(
index, size, type, stride,
reinterpret_cast<void*>(static_cast<intptr_t>(offset)));
}
/* Writing to the drawing buffer */
void WebGL2RenderingContextBase::vertexAttribDivisor(GLuint index,
GLuint divisor) {
if (isContextLost())
return;
if (index >= m_maxVertexAttribs) {
synthesizeGLError(GL_INVALID_VALUE, "vertexAttribDivisor",
"index out of range");
return;
}
contextGL()->VertexAttribDivisorANGLE(index, divisor);
}
void WebGL2RenderingContextBase::drawArraysInstanced(GLenum mode,
GLint first,
GLsizei count,
GLsizei instanceCount) {
if (!validateDrawArrays("drawArraysInstanced"))
return;
if (!m_boundVertexArrayObject->isAllEnabledAttribBufferBound()) {
synthesizeGLError(GL_INVALID_OPERATION, "drawArraysInstanced",
"no buffer is bound to enabled attribute");
return;
}
ScopedRGBEmulationColorMask emulationColorMask(contextGL(), m_colorMask,
m_drawingBuffer.get());
clearIfComposited();
contextGL()->DrawArraysInstancedANGLE(mode, first, count, instanceCount);
markContextChanged(CanvasChanged);
}
void WebGL2RenderingContextBase::drawElementsInstanced(GLenum mode,
GLsizei count,
GLenum type,
long long offset,
GLsizei instanceCount) {
if (!validateDrawElements("drawElementsInstanced", type, offset))
return;
if (!m_boundVertexArrayObject->isAllEnabledAttribBufferBound()) {
synthesizeGLError(GL_INVALID_OPERATION, "drawElementsInstanced",
"no buffer is bound to enabled attribute");
return;
}
ScopedRGBEmulationColorMask emulationColorMask(contextGL(), m_colorMask,
m_drawingBuffer.get());
clearIfComposited();
contextGL()->DrawElementsInstancedANGLE(
mode, count, type, reinterpret_cast<void*>(static_cast<intptr_t>(offset)),
instanceCount);
markContextChanged(CanvasChanged);
}
void WebGL2RenderingContextBase::drawRangeElements(GLenum mode,
GLuint start,
GLuint end,
GLsizei count,
GLenum type,
long long offset) {
if (!validateDrawElements("drawRangeElements", type, offset))
return;
if (!m_boundVertexArrayObject->isAllEnabledAttribBufferBound()) {
synthesizeGLError(GL_INVALID_OPERATION, "drawRangeElements",
"no buffer is bound to enabled attribute");
return;
}
ScopedRGBEmulationColorMask emulationColorMask(contextGL(), m_colorMask,
m_drawingBuffer.get());
clearIfComposited();
contextGL()->DrawRangeElements(
mode, start, end, count, type,
reinterpret_cast<void*>(static_cast<intptr_t>(offset)));
markContextChanged(CanvasChanged);
}
void WebGL2RenderingContextBase::drawBuffers(const Vector<GLenum>& buffers) {
if (isContextLost())
return;
ScopedRGBEmulationColorMask emulationColorMask(contextGL(), m_colorMask,
m_drawingBuffer.get());
GLsizei n = buffers.size();
const GLenum* bufs = buffers.data();
for (GLsizei i = 0; i < n; ++i) {
switch (bufs[i]) {
case GL_NONE:
case GL_BACK:
case GL_COLOR_ATTACHMENT0:
break;
default:
if (bufs[i] > GL_COLOR_ATTACHMENT0 &&
bufs[i] < static_cast<GLenum>(GL_COLOR_ATTACHMENT0 +
maxColorAttachments()))
break;
synthesizeGLError(GL_INVALID_ENUM, "drawBuffers", "invalid buffer");
return;
}
}
if (!m_framebufferBinding) {
if (n != 1) {
synthesizeGLError(GL_INVALID_OPERATION, "drawBuffers",
"the number of buffers is not 1");
return;
}
if (bufs[0] != GL_BACK && bufs[0] != GL_NONE) {
synthesizeGLError(GL_INVALID_OPERATION, "drawBuffers", "BACK or NONE");
return;
}
// Because the backbuffer is simulated on all current WebKit ports, we need
// to change BACK to COLOR_ATTACHMENT0.
GLenum value = (bufs[0] == GL_BACK) ? GL_COLOR_ATTACHMENT0 : GL_NONE;
contextGL()->DrawBuffersEXT(1, &value);
setBackDrawBuffer(bufs[0]);
} else {
if (n > maxDrawBuffers()) {
synthesizeGLError(GL_INVALID_VALUE, "drawBuffers",
"more than max draw buffers");
return;
}
for (GLsizei i = 0; i < n; ++i) {
if (bufs[i] != GL_NONE &&
bufs[i] != static_cast<GLenum>(GL_COLOR_ATTACHMENT0_EXT + i)) {
synthesizeGLError(GL_INVALID_OPERATION, "drawBuffers",
"COLOR_ATTACHMENTi_EXT or NONE");
return;
}
}
m_framebufferBinding->drawBuffers(buffers);
}
}
bool WebGL2RenderingContextBase::validateClearBuffer(const char* functionName,
GLenum buffer,
GLsizei size) {
switch (buffer) {
case GL_COLOR:
if (size < 4) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "invalid array size");
return false;
}
break;
case GL_DEPTH:
case GL_STENCIL:
if (size < 1) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "invalid array size");
return false;
}
break;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid buffer");
return false;
}
return true;
}
WebGLTexture* WebGL2RenderingContextBase::validateTexImageBinding(
const char* funcName,
TexImageFunctionID functionID,
GLenum target) {
if (functionID == TexImage3D || functionID == TexSubImage3D)
return validateTexture3DBinding(funcName, target);
return validateTexture2DBinding(funcName, target);
}
void WebGL2RenderingContextBase::clearBufferiv(GLenum buffer,
GLint drawbuffer,
DOMInt32Array* value) {
if (isContextLost() ||
!validateClearBuffer("clearBufferiv", buffer, value->length()))
return;
contextGL()->ClearBufferiv(buffer, drawbuffer, value->data());
}
void WebGL2RenderingContextBase::clearBufferiv(GLenum buffer,
GLint drawbuffer,
const Vector<GLint>& value) {
if (isContextLost() ||
!validateClearBuffer("clearBufferiv", buffer, value.size()))
return;
contextGL()->ClearBufferiv(buffer, drawbuffer, value.data());
}
void WebGL2RenderingContextBase::clearBufferuiv(GLenum buffer,
GLint drawbuffer,
DOMUint32Array* value) {
if (isContextLost() ||
!validateClearBuffer("clearBufferuiv", buffer, value->length()))
return;
contextGL()->ClearBufferuiv(buffer, drawbuffer, value->data());
}
void WebGL2RenderingContextBase::clearBufferuiv(GLenum buffer,
GLint drawbuffer,
const Vector<GLuint>& value) {
if (isContextLost() ||
!validateClearBuffer("clearBufferuiv", buffer, value.size()))
return;
contextGL()->ClearBufferuiv(buffer, drawbuffer, value.data());
}
void WebGL2RenderingContextBase::clearBufferfv(GLenum buffer,
GLint drawbuffer,
DOMFloat32Array* value) {
if (isContextLost() ||
!validateClearBuffer("clearBufferfv", buffer, value->length()))
return;
contextGL()->ClearBufferfv(buffer, drawbuffer, value->data());
}
void WebGL2RenderingContextBase::clearBufferfv(GLenum buffer,
GLint drawbuffer,
const Vector<GLfloat>& value) {
if (isContextLost() ||
!validateClearBuffer("clearBufferfv", buffer, value.size()))
return;
contextGL()->ClearBufferfv(buffer, drawbuffer, value.data());
}
void WebGL2RenderingContextBase::clearBufferfi(GLenum buffer,
GLint drawbuffer,
GLfloat depth,
GLint stencil) {
if (isContextLost())
return;
contextGL()->ClearBufferfi(buffer, drawbuffer, depth, stencil);
}
WebGLQuery* WebGL2RenderingContextBase::createQuery() {
if (isContextLost())
return nullptr;
WebGLQuery* o = WebGLQuery::create(this);
addSharedObject(o);
return o;
}
void WebGL2RenderingContextBase::deleteQuery(WebGLQuery* query) {
if (isContextLost() || !query)
return;
if (m_currentBooleanOcclusionQuery == query) {
contextGL()->EndQueryEXT(m_currentBooleanOcclusionQuery->getTarget());
m_currentBooleanOcclusionQuery = nullptr;
}
if (m_currentTransformFeedbackPrimitivesWrittenQuery == query) {
contextGL()->EndQueryEXT(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
m_currentTransformFeedbackPrimitivesWrittenQuery = nullptr;
}
deleteObject(query);
}
GLboolean WebGL2RenderingContextBase::isQuery(WebGLQuery* query) {
if (isContextLost() || !query)
return 0;
return contextGL()->IsQueryEXT(query->object());
}
void WebGL2RenderingContextBase::beginQuery(GLenum target, WebGLQuery* query) {
bool deleted;
if (!query) {
synthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
"query object is null");
return;
}
if (!checkObjectToBeBound("beginQuery", query, deleted))
return;
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
"attempted to begin a deleted query object");
return;
}
if (query->getTarget() && query->getTarget() != target) {
synthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
"query type does not match target");
return;
}
switch (target) {
case GL_ANY_SAMPLES_PASSED:
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: {
if (m_currentBooleanOcclusionQuery) {
synthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
"a query is already active for target");
return;
}
m_currentBooleanOcclusionQuery = query;
} break;
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: {
if (m_currentTransformFeedbackPrimitivesWrittenQuery) {
synthesizeGLError(GL_INVALID_OPERATION, "beginQuery",
"a query is already active for target");
return;
}
m_currentTransformFeedbackPrimitivesWrittenQuery = query;
} break;
default:
synthesizeGLError(GL_INVALID_ENUM, "beginQuery", "invalid target");
return;
}
if (!query->getTarget())
query->setTarget(target);
contextGL()->BeginQueryEXT(target, query->object());
}
void WebGL2RenderingContextBase::endQuery(GLenum target) {
if (isContextLost())
return;
switch (target) {
case GL_ANY_SAMPLES_PASSED:
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: {
if (m_currentBooleanOcclusionQuery &&
m_currentBooleanOcclusionQuery->getTarget() == target) {
m_currentBooleanOcclusionQuery->resetCachedResult();
m_currentBooleanOcclusionQuery = nullptr;
} else {
synthesizeGLError(GL_INVALID_OPERATION, "endQuery",
"target query is not active");
return;
}
} break;
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: {
if (m_currentTransformFeedbackPrimitivesWrittenQuery) {
m_currentTransformFeedbackPrimitivesWrittenQuery->resetCachedResult();
m_currentTransformFeedbackPrimitivesWrittenQuery = nullptr;
} else {
synthesizeGLError(GL_INVALID_OPERATION, "endQuery",
"target query is not active");
return;
}
} break;
default:
synthesizeGLError(GL_INVALID_ENUM, "endQuery", "invalid target");
return;
}
contextGL()->EndQueryEXT(target);
}
WebGLQuery* WebGL2RenderingContextBase::getQuery(GLenum target, GLenum pname) {
if (isContextLost())
return nullptr;
if (pname != GL_CURRENT_QUERY) {
synthesizeGLError(GL_INVALID_ENUM, "getQuery", "invalid parameter name");
return nullptr;
}
switch (target) {
case GL_ANY_SAMPLES_PASSED:
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
if (m_currentBooleanOcclusionQuery &&
m_currentBooleanOcclusionQuery->getTarget() == target)
return m_currentBooleanOcclusionQuery;
break;
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
return m_currentTransformFeedbackPrimitivesWrittenQuery;
default:
synthesizeGLError(GL_INVALID_ENUM, "getQuery", "invalid target");
return nullptr;
}
return nullptr;
}
ScriptValue WebGL2RenderingContextBase::getQueryParameter(
ScriptState* scriptState,
WebGLQuery* query,
GLenum pname) {
bool deleted;
if (!query) {
synthesizeGLError(GL_INVALID_OPERATION, "getQueryParameter",
"query object is null");
return ScriptValue::createNull(scriptState);
}
if (!checkObjectToBeBound("getQueryParameter", query, deleted))
return ScriptValue::createNull(scriptState);
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "getQueryParameter",
"attempted to access to a deleted query object");
return ScriptValue::createNull(scriptState);
}
// Query is non-null at this point.
if (!query->getTarget()) {
synthesizeGLError(GL_INVALID_OPERATION, "getQueryParameter",
"'query' is not a query object yet, since it has't been "
"used by beginQuery");
return ScriptValue::createNull(scriptState);
}
if (query == m_currentBooleanOcclusionQuery ||
query == m_currentTransformFeedbackPrimitivesWrittenQuery) {
synthesizeGLError(GL_INVALID_OPERATION, "getQueryParameter",
"query is currently active");
return ScriptValue::createNull(scriptState);
}
switch (pname) {
case GL_QUERY_RESULT: {
query->updateCachedResult(contextGL());
return WebGLAny(scriptState, query->getQueryResult());
}
case GL_QUERY_RESULT_AVAILABLE: {
query->updateCachedResult(contextGL());
return WebGLAny(scriptState, query->isQueryResultAvailable());
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getQueryParameter",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
WebGLSampler* WebGL2RenderingContextBase::createSampler() {
if (isContextLost())
return nullptr;
WebGLSampler* o = WebGLSampler::create(this);
addSharedObject(o);
return o;
}
void WebGL2RenderingContextBase::deleteSampler(WebGLSampler* sampler) {
if (isContextLost())
return;
for (size_t i = 0; i < m_samplerUnits.size(); ++i) {
if (sampler == m_samplerUnits[i]) {
m_samplerUnits[i] = nullptr;
contextGL()->BindSampler(i, 0);
}
}
deleteObject(sampler);
}
GLboolean WebGL2RenderingContextBase::isSampler(WebGLSampler* sampler) {
if (isContextLost() || !sampler)
return 0;
return contextGL()->IsSampler(sampler->object());
}
void WebGL2RenderingContextBase::bindSampler(GLuint unit,
WebGLSampler* sampler) {
if (isContextLost())
return;
bool deleted;
if (!checkObjectToBeBound("bindSampler", sampler, deleted))
return;
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "bindSampler",
"attempted to bind a deleted sampler");
return;
}
if (unit >= m_samplerUnits.size()) {
synthesizeGLError(GL_INVALID_VALUE, "bindSampler",
"texture unit out of range");
return;
}
m_samplerUnits[unit] = sampler;
contextGL()->BindSampler(unit, objectOrZero(sampler));
}
void WebGL2RenderingContextBase::samplerParameter(WebGLSampler* sampler,
GLenum pname,
GLfloat paramf,
GLint parami,
bool isFloat) {
if (isContextLost() || !validateWebGLObject("samplerParameter", sampler))
return;
GLint param = isFloat ? static_cast<GLint>(paramf) : parami;
switch (pname) {
case GL_TEXTURE_MAX_LOD:
case GL_TEXTURE_MIN_LOD:
break;
case GL_TEXTURE_COMPARE_FUNC:
switch (param) {
case GL_LEQUAL:
case GL_GEQUAL:
case GL_LESS:
case GL_GREATER:
case GL_EQUAL:
case GL_NOTEQUAL:
case GL_ALWAYS:
case GL_NEVER:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "samplerParameter",
"invalid parameter");
return;
}
break;
case GL_TEXTURE_COMPARE_MODE:
switch (param) {
case GL_COMPARE_REF_TO_TEXTURE:
case GL_NONE:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "samplerParameter",
"invalid parameter");
return;
}
break;
case GL_TEXTURE_MAG_FILTER:
switch (param) {
case GL_NEAREST:
case GL_LINEAR:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "samplerParameter",
"invalid parameter");
return;
}
break;
case GL_TEXTURE_MIN_FILTER:
switch (param) {
case GL_NEAREST:
case GL_LINEAR:
case GL_NEAREST_MIPMAP_NEAREST:
case GL_LINEAR_MIPMAP_NEAREST:
case GL_NEAREST_MIPMAP_LINEAR:
case GL_LINEAR_MIPMAP_LINEAR:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "samplerParameter",
"invalid parameter");
return;
}
break;
case GL_TEXTURE_WRAP_R:
case GL_TEXTURE_WRAP_S:
case GL_TEXTURE_WRAP_T:
switch (param) {
case GL_CLAMP_TO_EDGE:
case GL_MIRRORED_REPEAT:
case GL_REPEAT:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "samplerParameter",
"invalid parameter");
return;
}
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "samplerParameter",
"invalid parameter name");
return;
}
if (isFloat) {
contextGL()->SamplerParameterf(objectOrZero(sampler), pname, paramf);
} else {
contextGL()->SamplerParameteri(objectOrZero(sampler), pname, parami);
}
}
void WebGL2RenderingContextBase::samplerParameteri(WebGLSampler* sampler,
GLenum pname,
GLint param) {
samplerParameter(sampler, pname, 0, param, false);
}
void WebGL2RenderingContextBase::samplerParameterf(WebGLSampler* sampler,
GLenum pname,
GLfloat param) {
samplerParameter(sampler, pname, param, 0, true);
}
ScriptValue WebGL2RenderingContextBase::getSamplerParameter(
ScriptState* scriptState,
WebGLSampler* sampler,
GLenum pname) {
if (isContextLost() || !validateWebGLObject("getSamplerParameter", sampler))
return ScriptValue::createNull(scriptState);
switch (pname) {
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 value = 0;
contextGL()->GetSamplerParameteriv(objectOrZero(sampler), pname, &value);
return WebGLAny(scriptState, static_cast<unsigned>(value));
}
case GL_TEXTURE_MAX_LOD:
case GL_TEXTURE_MIN_LOD: {
GLfloat value = 0.f;
contextGL()->GetSamplerParameterfv(objectOrZero(sampler), pname, &value);
return WebGLAny(scriptState, value);
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getSamplerParameter",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
WebGLSync* WebGL2RenderingContextBase::fenceSync(GLenum condition,
GLbitfield flags) {
if (isContextLost())
return nullptr;
WebGLSync* o = WebGLFenceSync::create(this, condition, flags);
addSharedObject(o);
return o;
}
GLboolean WebGL2RenderingContextBase::isSync(WebGLSync* sync) {
if (isContextLost() || !sync)
return 0;
return contextGL()->IsSync(sync->object());
}
void WebGL2RenderingContextBase::deleteSync(WebGLSync* sync) {
deleteObject(sync);
}
GLenum WebGL2RenderingContextBase::clientWaitSync(WebGLSync* sync,
GLbitfield flags,
GLint64 timeout) {
if (isContextLost() || !validateWebGLObject("clientWaitSync", sync))
return GL_WAIT_FAILED;
if (timeout < -1) {
synthesizeGLError(GL_INVALID_VALUE, "clientWaitSync", "timeout < -1");
return GL_WAIT_FAILED;
}
GLuint64 timeout64 =
timeout == -1 ? GL_TIMEOUT_IGNORED : static_cast<GLuint64>(timeout);
return contextGL()->ClientWaitSync(syncObjectOrZero(sync), flags, timeout64);
}
void WebGL2RenderingContextBase::waitSync(WebGLSync* sync,
GLbitfield flags,
GLint64 timeout) {
if (isContextLost() || !validateWebGLObject("waitSync", sync))
return;
if (timeout < -1) {
synthesizeGLError(GL_INVALID_VALUE, "waitSync", "timeout < -1");
return;
}
GLuint64 timeout64 =
timeout == -1 ? GL_TIMEOUT_IGNORED : static_cast<GLuint64>(timeout);
contextGL()->WaitSync(syncObjectOrZero(sync), flags, timeout64);
}
ScriptValue WebGL2RenderingContextBase::getSyncParameter(
ScriptState* scriptState,
WebGLSync* sync,
GLenum pname) {
if (isContextLost() || !validateWebGLObject("getSyncParameter", sync))
return ScriptValue::createNull(scriptState);
switch (pname) {
case GL_OBJECT_TYPE:
case GL_SYNC_STATUS:
case GL_SYNC_CONDITION:
case GL_SYNC_FLAGS: {
GLint value = 0;
GLsizei length = -1;
contextGL()->GetSynciv(syncObjectOrZero(sync), pname, 1, &length, &value);
return WebGLAny(scriptState, static_cast<unsigned>(value));
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getSyncParameter",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
WebGLTransformFeedback* WebGL2RenderingContextBase::createTransformFeedback() {
if (isContextLost())
return nullptr;
WebGLTransformFeedback* o = WebGLTransformFeedback::create(this);
addSharedObject(o);
return o;
}
void WebGL2RenderingContextBase::deleteTransformFeedback(
WebGLTransformFeedback* feedback) {
if (feedback == m_transformFeedbackBinding)
m_transformFeedbackBinding = nullptr;
deleteObject(feedback);
}
GLboolean WebGL2RenderingContextBase::isTransformFeedback(
WebGLTransformFeedback* feedback) {
if (isContextLost() || !feedback)
return 0;
if (!feedback->hasEverBeenBound())
return 0;
return contextGL()->IsTransformFeedback(feedback->object());
}
void WebGL2RenderingContextBase::bindTransformFeedback(
GLenum target,
WebGLTransformFeedback* feedback) {
bool deleted;
if (!checkObjectToBeBound("bindTransformFeedback", feedback, deleted))
return;
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "bindTransformFeedback",
"attempted to bind a deleted transform feedback object");
return;
}
if (target != GL_TRANSFORM_FEEDBACK) {
synthesizeGLError(GL_INVALID_ENUM, "bindTransformFeedback",
"target must be TRANSFORM_FEEDBACK");
return;
}
m_transformFeedbackBinding = feedback;
contextGL()->BindTransformFeedback(target, objectOrZero(feedback));
if (feedback) {
feedback->setTarget(target);
}
}
void WebGL2RenderingContextBase::beginTransformFeedback(GLenum primitiveMode) {
if (isContextLost())
return;
if (!validateTransformFeedbackPrimitiveMode("beginTransformFeedback",
primitiveMode))
return;
contextGL()->BeginTransformFeedback(primitiveMode);
if (m_currentProgram)
m_currentProgram->increaseActiveTransformFeedbackCount();
if (m_transformFeedbackBinding)
m_transformFeedbackBinding->setProgram(m_currentProgram);
}
void WebGL2RenderingContextBase::endTransformFeedback() {
if (isContextLost())
return;
contextGL()->EndTransformFeedback();
if (m_currentProgram)
m_currentProgram->decreaseActiveTransformFeedbackCount();
}
void WebGL2RenderingContextBase::transformFeedbackVaryings(
WebGLProgram* program,
const Vector<String>& varyings,
GLenum bufferMode) {
if (isContextLost() ||
!validateWebGLObject("transformFeedbackVaryings", program))
return;
switch (bufferMode) {
case GL_SEPARATE_ATTRIBS:
if (varyings.size() >
static_cast<size_t>(m_maxTransformFeedbackSeparateAttribs)) {
synthesizeGLError(GL_INVALID_VALUE, "transformFeedbackVaryings",
"too many varyings");
return;
}
break;
case GL_INTERLEAVED_ATTRIBS:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "transformFeedbackVaryings",
"invalid buffer mode");
return;
}
Vector<CString>
keepAlive; // Must keep these instances alive while looking at their data
Vector<const char*> varyingStrings;
for (size_t i = 0; i < varyings.size(); ++i) {
keepAlive.append(varyings[i].ascii());
varyingStrings.append(keepAlive.last().data());
}
contextGL()->TransformFeedbackVaryings(objectOrZero(program), varyings.size(),
varyingStrings.data(), bufferMode);
}
WebGLActiveInfo* WebGL2RenderingContextBase::getTransformFeedbackVarying(
WebGLProgram* program,
GLuint index) {
if (isContextLost() ||
!validateWebGLObject("getTransformFeedbackVarying", program))
return nullptr;
if (!program->linkStatus(this)) {
synthesizeGLError(GL_INVALID_OPERATION, "getTransformFeedbackVarying",
"program not linked");
return nullptr;
}
GLint maxIndex = 0;
contextGL()->GetProgramiv(objectOrZero(program),
GL_TRANSFORM_FEEDBACK_VARYINGS, &maxIndex);
if (index >= static_cast<GLuint>(maxIndex)) {
synthesizeGLError(GL_INVALID_VALUE, "getTransformFeedbackVarying",
"invalid index");
return nullptr;
}
GLint maxNameLength = -1;
contextGL()->GetProgramiv(objectOrZero(program),
GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH,
&maxNameLength);
if (maxNameLength <= 0) {
return nullptr;
}
std::unique_ptr<GLchar[]> name = wrapArrayUnique(new GLchar[maxNameLength]);
GLsizei length = 0;
GLsizei size = 0;
GLenum type = 0;
contextGL()->GetTransformFeedbackVarying(objectOrZero(program), index,
maxNameLength, &length, &size, &type,
name.get());
if (length == 0 || size == 0 || type == 0) {
return nullptr;
}
return WebGLActiveInfo::create(String(name.get(), length), type, size);
}
void WebGL2RenderingContextBase::pauseTransformFeedback() {
if (isContextLost())
return;
contextGL()->PauseTransformFeedback();
}
void WebGL2RenderingContextBase::resumeTransformFeedback() {
if (isContextLost())
return;
if (m_transformFeedbackBinding &&
m_transformFeedbackBinding->getProgram() != m_currentProgram) {
synthesizeGLError(GL_INVALID_OPERATION, "resumeTransformFeedback",
"the program object is not active");
return;
}
contextGL()->ResumeTransformFeedback();
}
bool WebGL2RenderingContextBase::validateTransformFeedbackPrimitiveMode(
const char* functionName,
GLenum primitiveMode) {
switch (primitiveMode) {
case GL_POINTS:
case GL_LINES:
case GL_TRIANGLES:
return true;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName,
"invalid transform feedback primitive mode");
return false;
}
}
void WebGL2RenderingContextBase::bindBufferBase(GLenum target,
GLuint index,
WebGLBuffer* buffer) {
if (isContextLost())
return;
bool deleted;
if (!checkObjectToBeBound("bindBufferBase", buffer, deleted))
return;
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "bindBufferBase",
"attempt to bind a deleted buffer");
return;
}
if (!validateAndUpdateBufferBindBaseTarget("bindBufferBase", target, index,
buffer))
return;
contextGL()->BindBufferBase(target, index, objectOrZero(buffer));
}
void WebGL2RenderingContextBase::bindBufferRange(GLenum target,
GLuint index,
WebGLBuffer* buffer,
long long offset,
long long size) {
if (isContextLost())
return;
bool deleted;
if (!checkObjectToBeBound("bindBufferRange", buffer, deleted))
return;
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "bindBufferRange",
"attempt to bind a deleted buffer");
return;
}
if (!validateValueFitNonNegInt32("bindBufferRange", "offset", offset) ||
!validateValueFitNonNegInt32("bindBufferRange", "size", size)) {
return;
}
if (!validateAndUpdateBufferBindBaseTarget("bindBufferRange", target, index,
buffer))
return;
contextGL()->BindBufferRange(target, index, objectOrZero(buffer),
static_cast<GLintptr>(offset),
static_cast<GLsizeiptr>(size));
}
ScriptValue WebGL2RenderingContextBase::getIndexedParameter(
ScriptState* scriptState,
GLenum target,
GLuint index) {
if (isContextLost())
return ScriptValue::createNull(scriptState);
switch (target) {
case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
if (index >= m_boundIndexedTransformFeedbackBuffers.size()) {
synthesizeGLError(GL_INVALID_VALUE, "getIndexedParameter",
"index out of range");
return ScriptValue::createNull(scriptState);
}
return WebGLAny(scriptState,
m_boundIndexedTransformFeedbackBuffers[index].get());
case GL_UNIFORM_BUFFER_BINDING:
if (index >= m_boundIndexedUniformBuffers.size()) {
synthesizeGLError(GL_INVALID_VALUE, "getIndexedParameter",
"index out of range");
return ScriptValue::createNull(scriptState);
}
return WebGLAny(scriptState, m_boundIndexedUniformBuffers[index].get());
case GL_TRANSFORM_FEEDBACK_BUFFER_SIZE:
case GL_TRANSFORM_FEEDBACK_BUFFER_START:
case GL_UNIFORM_BUFFER_SIZE:
case GL_UNIFORM_BUFFER_START: {
GLint64 value = -1;
contextGL()->GetInteger64i_v(target, index, &value);
return WebGLAny(scriptState, value);
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getIndexedParameter",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
Vector<GLuint> WebGL2RenderingContextBase::getUniformIndices(
WebGLProgram* program,
const Vector<String>& uniformNames) {
Vector<GLuint> result;
if (isContextLost() || !validateWebGLObject("getUniformIndices", program))
return result;
Vector<CString>
keepAlive; // Must keep these instances alive while looking at their data
Vector<const char*> uniformStrings;
for (size_t i = 0; i < uniformNames.size(); ++i) {
keepAlive.append(uniformNames[i].ascii());
uniformStrings.append(keepAlive.last().data());
}
result.resize(uniformNames.size());
contextGL()->GetUniformIndices(objectOrZero(program), uniformStrings.size(),
uniformStrings.data(), result.data());
return result;
}
ScriptValue WebGL2RenderingContextBase::getActiveUniforms(
ScriptState* scriptState,
WebGLProgram* program,
const Vector<GLuint>& uniformIndices,
GLenum pname) {
if (isContextLost() || !validateWebGLObject("getActiveUniforms", program))
return ScriptValue::createNull(scriptState);
enum ReturnType { EnumType, UnsignedIntType, IntType, BoolType };
int returnType;
switch (pname) {
case GL_UNIFORM_TYPE:
returnType = EnumType;
break;
case GL_UNIFORM_SIZE:
returnType = UnsignedIntType;
break;
case GL_UNIFORM_BLOCK_INDEX:
case GL_UNIFORM_OFFSET:
case GL_UNIFORM_ARRAY_STRIDE:
case GL_UNIFORM_MATRIX_STRIDE:
returnType = IntType;
break;
case GL_UNIFORM_IS_ROW_MAJOR:
returnType = BoolType;
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "getActiveUniforms",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
GLint activeUniforms = -1;
contextGL()->GetProgramiv(objectOrZero(program), GL_ACTIVE_UNIFORMS,
&activeUniforms);
GLuint activeUniformsUnsigned = activeUniforms;
size_t size = uniformIndices.size();
for (size_t i = 0; i < size; ++i) {
if (uniformIndices[i] >= activeUniformsUnsigned) {
synthesizeGLError(GL_INVALID_VALUE, "getActiveUniforms",
"uniform index greater than ACTIVE_UNIFORMS");
return ScriptValue::createNull(scriptState);
}
}
Vector<GLint> result(size);
contextGL()->GetActiveUniformsiv(objectOrZero(program), uniformIndices.size(),
uniformIndices.data(), pname, result.data());
switch (returnType) {
case EnumType: {
Vector<GLenum> enumResult(size);
for (size_t i = 0; i < size; ++i)
enumResult[i] = static_cast<GLenum>(result[i]);
return WebGLAny(scriptState, enumResult);
}
case UnsignedIntType: {
Vector<GLuint> uintResult(size);
for (size_t i = 0; i < size; ++i)
uintResult[i] = static_cast<GLuint>(result[i]);
return WebGLAny(scriptState, uintResult);
}
case IntType: {
return WebGLAny(scriptState, result);
}
case BoolType: {
Vector<bool> boolResult(size);
for (size_t i = 0; i < size; ++i)
boolResult[i] = static_cast<bool>(result[i]);
return WebGLAny(scriptState, boolResult);
}
default:
ASSERT_NOT_REACHED();
return ScriptValue::createNull(scriptState);
}
}
GLuint WebGL2RenderingContextBase::getUniformBlockIndex(
WebGLProgram* program,
const String& uniformBlockName) {
if (isContextLost() || !validateWebGLObject("getUniformBlockIndex", program))
return 0;
if (!validateString("getUniformBlockIndex", uniformBlockName))
return 0;
return contextGL()->GetUniformBlockIndex(objectOrZero(program),
uniformBlockName.utf8().data());
}
bool WebGL2RenderingContextBase::validateUniformBlockIndex(
const char* functionName,
WebGLProgram* program,
GLuint blockIndex) {
ASSERT(program);
if (!program->linkStatus(this)) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "program not linked");
return false;
}
GLint activeUniformBlocks = 0;
contextGL()->GetProgramiv(objectOrZero(program), GL_ACTIVE_UNIFORM_BLOCKS,
&activeUniformBlocks);
if (blockIndex >= static_cast<GLuint>(activeUniformBlocks)) {
synthesizeGLError(GL_INVALID_VALUE, functionName,
"invalid uniform block index");
return false;
}
return true;
}
ScriptValue WebGL2RenderingContextBase::getActiveUniformBlockParameter(
ScriptState* scriptState,
WebGLProgram* program,
GLuint uniformBlockIndex,
GLenum pname) {
if (isContextLost() ||
!validateWebGLObject("getActiveUniformBlockParameter", program))
return ScriptValue::createNull(scriptState);
if (!validateUniformBlockIndex("getActiveUniformBlockParameter", program,
uniformBlockIndex))
return ScriptValue::createNull(scriptState);
switch (pname) {
case GL_UNIFORM_BLOCK_BINDING:
case GL_UNIFORM_BLOCK_DATA_SIZE:
case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: {
GLint intValue = 0;
contextGL()->GetActiveUniformBlockiv(objectOrZero(program),
uniformBlockIndex, pname, &intValue);
return WebGLAny(scriptState, static_cast<unsigned>(intValue));
}
case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
GLint uniformCount = 0;
contextGL()->GetActiveUniformBlockiv(
objectOrZero(program), uniformBlockIndex,
GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &uniformCount);
Vector<GLint> indices(uniformCount);
contextGL()->GetActiveUniformBlockiv(
objectOrZero(program), uniformBlockIndex, pname, indices.data());
return WebGLAny(
scriptState,
DOMUint32Array::create(reinterpret_cast<GLuint*>(indices.data()),
indices.size()));
}
case GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
case GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER: {
GLint boolValue = 0;
contextGL()->GetActiveUniformBlockiv(
objectOrZero(program), uniformBlockIndex, pname, &boolValue);
return WebGLAny(scriptState, static_cast<bool>(boolValue));
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getActiveUniformBlockParameter",
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
String WebGL2RenderingContextBase::getActiveUniformBlockName(
WebGLProgram* program,
GLuint uniformBlockIndex) {
if (isContextLost() ||
!validateWebGLObject("getActiveUniformBlockName", program))
return String();
if (!validateUniformBlockIndex("getActiveUniformBlockName", program,
uniformBlockIndex))
return String();
GLint maxNameLength = -1;
contextGL()->GetProgramiv(objectOrZero(program),
GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH,
&maxNameLength);
if (maxNameLength <= 0) {
// This state indicates that there are no active uniform blocks
synthesizeGLError(GL_INVALID_VALUE, "getActiveUniformBlockName",
"invalid uniform block index");
return String();
}
std::unique_ptr<GLchar[]> name = wrapArrayUnique(new GLchar[maxNameLength]);
GLsizei length = 0;
contextGL()->GetActiveUniformBlockName(objectOrZero(program),
uniformBlockIndex, maxNameLength,
&length, name.get());
return String(name.get(), length);
}
void WebGL2RenderingContextBase::uniformBlockBinding(
WebGLProgram* program,
GLuint uniformBlockIndex,
GLuint uniformBlockBinding) {
if (isContextLost() || !validateWebGLObject("uniformBlockBinding", program))
return;
if (!validateUniformBlockIndex("uniformBlockBinding", program,
uniformBlockIndex))
return;
contextGL()->UniformBlockBinding(objectOrZero(program), uniformBlockIndex,
uniformBlockBinding);
}
WebGLVertexArrayObject* WebGL2RenderingContextBase::createVertexArray() {
if (isContextLost())
return nullptr;
WebGLVertexArrayObject* o = WebGLVertexArrayObject::create(
this, WebGLVertexArrayObjectBase::VaoTypeUser);
addContextObject(o);
return o;
}
void WebGL2RenderingContextBase::deleteVertexArray(
WebGLVertexArrayObject* vertexArray) {
if (isContextLost() || !vertexArray)
return;
if (!vertexArray->isDefaultObject() &&
vertexArray == m_boundVertexArrayObject)
setBoundVertexArrayObject(nullptr);
vertexArray->deleteObject(contextGL());
}
GLboolean WebGL2RenderingContextBase::isVertexArray(
WebGLVertexArrayObject* vertexArray) {
if (isContextLost() || !vertexArray)
return 0;
if (!vertexArray->hasEverBeenBound())
return 0;
return contextGL()->IsVertexArrayOES(vertexArray->object());
}
void WebGL2RenderingContextBase::bindVertexArray(
WebGLVertexArrayObject* vertexArray) {
if (isContextLost())
return;
if (vertexArray &&
(vertexArray->isDeleted() || !vertexArray->validate(0, this))) {
synthesizeGLError(GL_INVALID_OPERATION, "bindVertexArray",
"invalid vertexArray");
return;
}
if (vertexArray && !vertexArray->isDefaultObject() && vertexArray->object()) {
contextGL()->BindVertexArrayOES(objectOrZero(vertexArray));
vertexArray->setHasEverBeenBound();
setBoundVertexArrayObject(vertexArray);
} else {
contextGL()->BindVertexArrayOES(0);
setBoundVertexArrayObject(nullptr);
}
}
void WebGL2RenderingContextBase::bindFramebuffer(GLenum target,
WebGLFramebuffer* buffer) {
bool deleted;
if (!checkObjectToBeBound("bindFramebuffer", buffer, deleted))
return;
if (deleted) {
synthesizeGLError(GL_INVALID_OPERATION, "bindFramebuffer",
"attempt to bind a deleted framebuffer");
return;
}
switch (target) {
case GL_DRAW_FRAMEBUFFER:
break;
case GL_FRAMEBUFFER:
case GL_READ_FRAMEBUFFER:
m_readFramebufferBinding = buffer;
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "bindFramebuffer", "invalid target");
return;
}
setFramebuffer(target, buffer);
}
void WebGL2RenderingContextBase::deleteFramebuffer(
WebGLFramebuffer* framebuffer) {
if (!deleteObject(framebuffer))
return;
GLenum target = 0;
if (framebuffer == m_framebufferBinding) {
if (framebuffer == m_readFramebufferBinding) {
target = GL_FRAMEBUFFER;
m_framebufferBinding = nullptr;
m_readFramebufferBinding = nullptr;
} else {
target = GL_DRAW_FRAMEBUFFER;
m_framebufferBinding = nullptr;
}
} else if (framebuffer == m_readFramebufferBinding) {
target = GL_READ_FRAMEBUFFER;
m_readFramebufferBinding = nullptr;
}
if (target) {
drawingBuffer()->setFramebufferBinding(target, 0);
// Have to call drawingBuffer()->bind() here to bind back to internal fbo.
drawingBuffer()->bind(target);
}
}
ScriptValue WebGL2RenderingContextBase::getParameter(ScriptState* scriptState,
GLenum pname) {
if (isContextLost())
return ScriptValue::createNull(scriptState);
switch (pname) {
case GL_SHADING_LANGUAGE_VERSION: {
return WebGLAny(
scriptState,
"WebGL GLSL ES 3.00 (" +
String(contextGL()->GetString(GL_SHADING_LANGUAGE_VERSION)) +
")");
}
case GL_VERSION:
return WebGLAny(
scriptState,
"WebGL 2.0 (" + String(contextGL()->GetString(GL_VERSION)) + ")");
case GL_COPY_READ_BUFFER_BINDING:
return WebGLAny(scriptState, m_boundCopyReadBuffer.get());
case GL_COPY_WRITE_BUFFER_BINDING:
return WebGLAny(scriptState, m_boundCopyWriteBuffer.get());
case GL_DRAW_FRAMEBUFFER_BINDING:
return WebGLAny(scriptState, m_framebufferBinding.get());
case GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
return getUnsignedIntParameter(scriptState, pname);
case GL_MAX_3D_TEXTURE_SIZE:
return getIntParameter(scriptState, pname);
case GL_MAX_ARRAY_TEXTURE_LAYERS:
return getIntParameter(scriptState, pname);
case GC3D_MAX_CLIENT_WAIT_TIMEOUT_WEBGL:
return WebGLAny(scriptState, 0u);
case GL_MAX_COLOR_ATTACHMENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS:
return getInt64Parameter(scriptState, pname);
case GL_MAX_COMBINED_UNIFORM_BLOCKS:
return getIntParameter(scriptState, pname);
case GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS:
return getInt64Parameter(scriptState, pname);
case GL_MAX_DRAW_BUFFERS:
return getIntParameter(scriptState, pname);
case GL_MAX_ELEMENT_INDEX:
return getInt64Parameter(scriptState, pname);
case GL_MAX_ELEMENTS_INDICES:
return getIntParameter(scriptState, pname);
case GL_MAX_ELEMENTS_VERTICES:
return getIntParameter(scriptState, pname);
case GL_MAX_FRAGMENT_INPUT_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_FRAGMENT_UNIFORM_BLOCKS:
return getIntParameter(scriptState, pname);
case GL_MAX_FRAGMENT_UNIFORM_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_PROGRAM_TEXEL_OFFSET:
return getIntParameter(scriptState, pname);
case GL_MAX_SAMPLES:
return getIntParameter(scriptState, pname);
case GL_MAX_SERVER_WAIT_TIMEOUT:
return getInt64Parameter(scriptState, pname);
case GL_MAX_TEXTURE_LOD_BIAS:
return getFloatParameter(scriptState, pname);
case GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
return getIntParameter(scriptState, pname);
case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_UNIFORM_BLOCK_SIZE:
return getInt64Parameter(scriptState, pname);
case GL_MAX_UNIFORM_BUFFER_BINDINGS:
return getIntParameter(scriptState, pname);
case GL_MAX_VARYING_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_VERTEX_OUTPUT_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MAX_VERTEX_UNIFORM_BLOCKS:
return getIntParameter(scriptState, pname);
case GL_MAX_VERTEX_UNIFORM_COMPONENTS:
return getIntParameter(scriptState, pname);
case GL_MIN_PROGRAM_TEXEL_OFFSET:
return getIntParameter(scriptState, pname);
case GL_PACK_ROW_LENGTH:
return getIntParameter(scriptState, pname);
case GL_PACK_SKIP_PIXELS:
return getIntParameter(scriptState, pname);
case GL_PACK_SKIP_ROWS:
return getIntParameter(scriptState, pname);
case GL_PIXEL_PACK_BUFFER_BINDING:
return WebGLAny(scriptState, m_boundPixelPackBuffer.get());
case GL_PIXEL_UNPACK_BUFFER_BINDING:
return WebGLAny(scriptState, m_boundPixelUnpackBuffer.get());
case GL_RASTERIZER_DISCARD:
return getBooleanParameter(scriptState, pname);
case GL_READ_BUFFER: {
GLenum value = 0;
if (!isContextLost()) {
WebGLFramebuffer* readFramebufferBinding =
getFramebufferBinding(GL_READ_FRAMEBUFFER);
if (!readFramebufferBinding)
value = m_readBufferOfDefaultFramebuffer;
else
value = readFramebufferBinding->getReadBuffer();
}
return WebGLAny(scriptState, value);
}
case GL_READ_FRAMEBUFFER_BINDING:
return WebGLAny(scriptState, m_readFramebufferBinding.get());
case GL_SAMPLE_ALPHA_TO_COVERAGE:
return getBooleanParameter(scriptState, pname);
case GL_SAMPLE_COVERAGE:
return getBooleanParameter(scriptState, pname);
case GL_SAMPLER_BINDING:
return WebGLAny(scriptState, m_samplerUnits[m_activeTextureUnit].get());
case GL_TEXTURE_BINDING_2D_ARRAY:
return WebGLAny(
scriptState,
m_textureUnits[m_activeTextureUnit].m_texture2DArrayBinding.get());
case GL_TEXTURE_BINDING_3D:
return WebGLAny(
scriptState,
m_textureUnits[m_activeTextureUnit].m_texture3DBinding.get());
case GL_TRANSFORM_FEEDBACK_ACTIVE:
return getBooleanParameter(scriptState, pname);
case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
return WebGLAny(scriptState, m_boundTransformFeedbackBuffer.get());
case GL_TRANSFORM_FEEDBACK_BINDING:
return WebGLAny(scriptState, m_transformFeedbackBinding.get());
case GL_TRANSFORM_FEEDBACK_PAUSED:
return getBooleanParameter(scriptState, pname);
case GL_UNIFORM_BUFFER_BINDING:
return WebGLAny(scriptState, m_boundUniformBuffer.get());
case GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
return getIntParameter(scriptState, pname);
case GL_UNPACK_IMAGE_HEIGHT:
return getIntParameter(scriptState, pname);
case GL_UNPACK_ROW_LENGTH:
return getIntParameter(scriptState, pname);
case GL_UNPACK_SKIP_IMAGES:
return getIntParameter(scriptState, pname);
case GL_UNPACK_SKIP_PIXELS:
return getIntParameter(scriptState, pname);
case GL_UNPACK_SKIP_ROWS:
return getIntParameter(scriptState, pname);
default:
return WebGLRenderingContextBase::getParameter(scriptState, pname);
}
}
ScriptValue WebGL2RenderingContextBase::getInt64Parameter(
ScriptState* scriptState,
GLenum pname) {
GLint64 value = 0;
if (!isContextLost())
contextGL()->GetInteger64v(pname, &value);
return WebGLAny(scriptState, value);
}
bool WebGL2RenderingContextBase::validateCapability(const char* functionName,
GLenum cap) {
switch (cap) {
case GL_RASTERIZER_DISCARD:
return true;
default:
return WebGLRenderingContextBase::validateCapability(functionName, cap);
}
}
bool WebGL2RenderingContextBase::isBufferBoundToTransformFeedback(
WebGLBuffer* buffer) {
ASSERT(buffer);
if (m_boundTransformFeedbackBuffer == buffer)
return true;
for (size_t i = 0; i < m_boundIndexedTransformFeedbackBuffers.size(); ++i) {
if (m_boundIndexedTransformFeedbackBuffers[i] == buffer)
return true;
}
return false;
}
bool WebGL2RenderingContextBase::isBufferBoundToNonTransformFeedback(
WebGLBuffer* buffer) {
ASSERT(buffer);
if (m_boundArrayBuffer == buffer ||
m_boundVertexArrayObject->boundElementArrayBuffer() == buffer ||
m_boundCopyReadBuffer == buffer || m_boundCopyWriteBuffer == buffer ||
m_boundPixelPackBuffer == buffer || m_boundPixelUnpackBuffer == buffer ||
m_boundUniformBuffer == buffer) {
return true;
}
for (size_t i = 0; i <= m_maxBoundUniformBufferIndex; ++i) {
if (m_boundIndexedUniformBuffers[i] == buffer)
return true;
}
return false;
}
bool WebGL2RenderingContextBase::validateBufferTargetCompatibility(
const char* functionName,
GLenum target,
WebGLBuffer* buffer) {
ASSERT(buffer);
switch (buffer->getInitialTarget()) {
case GL_ELEMENT_ARRAY_BUFFER:
switch (target) {
case GL_ARRAY_BUFFER:
case GL_PIXEL_PACK_BUFFER:
case GL_PIXEL_UNPACK_BUFFER:
case GL_TRANSFORM_FEEDBACK_BUFFER:
case GL_UNIFORM_BUFFER:
synthesizeGLError(
GL_INVALID_OPERATION, functionName,
"element array buffers can not be bound to a different target");
return false;
default:
break;
}
break;
case GL_ARRAY_BUFFER:
case GL_COPY_READ_BUFFER:
case GL_COPY_WRITE_BUFFER:
case GL_PIXEL_PACK_BUFFER:
case GL_PIXEL_UNPACK_BUFFER:
case GL_UNIFORM_BUFFER:
case GL_TRANSFORM_FEEDBACK_BUFFER:
if (target == GL_ELEMENT_ARRAY_BUFFER) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"buffers bound to non ELEMENT_ARRAY_BUFFER targets "
"can not be bound to ELEMENT_ARRAY_BUFFER target");
return false;
}
break;
default:
break;
}
if (target == GL_TRANSFORM_FEEDBACK_BUFFER) {
if (isBufferBoundToNonTransformFeedback(buffer)) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"a buffer bound to TRANSFORM_FEEDBACK_BUFFER can not "
"be bound to any other targets");
return false;
}
} else if (isBufferBoundToTransformFeedback(buffer)) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"a buffer bound to TRANSFORM_FEEDBACK_BUFFER can not be "
"bound to any other targets");
return false;
}
return true;
}
bool WebGL2RenderingContextBase::validateBufferTarget(const char* functionName,
GLenum target) {
switch (target) {
case GL_ARRAY_BUFFER:
case GL_COPY_READ_BUFFER:
case GL_COPY_WRITE_BUFFER:
case GL_ELEMENT_ARRAY_BUFFER:
case GL_PIXEL_PACK_BUFFER:
case GL_PIXEL_UNPACK_BUFFER:
case GL_TRANSFORM_FEEDBACK_BUFFER:
case GL_UNIFORM_BUFFER:
return true;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return false;
}
}
bool WebGL2RenderingContextBase::validateAndUpdateBufferBindTarget(
const char* functionName,
GLenum target,
WebGLBuffer* buffer) {
if (!validateBufferTarget(functionName, target))
return false;
if (buffer &&
!validateBufferTargetCompatibility(functionName, target, buffer))
return false;
switch (target) {
case GL_ARRAY_BUFFER:
m_boundArrayBuffer = buffer;
break;
case GL_COPY_READ_BUFFER:
m_boundCopyReadBuffer = buffer;
break;
case GL_COPY_WRITE_BUFFER:
m_boundCopyWriteBuffer = buffer;
break;
case GL_ELEMENT_ARRAY_BUFFER:
m_boundVertexArrayObject->setElementArrayBuffer(buffer);
break;
case GL_PIXEL_PACK_BUFFER:
m_boundPixelPackBuffer = buffer;
break;
case GL_PIXEL_UNPACK_BUFFER:
m_boundPixelUnpackBuffer = buffer;
break;
case GL_TRANSFORM_FEEDBACK_BUFFER:
m_boundTransformFeedbackBuffer = buffer;
break;
case GL_UNIFORM_BUFFER:
m_boundUniformBuffer = buffer;
break;
default:
ASSERT_NOT_REACHED();
break;
}
if (buffer && !buffer->getInitialTarget())
buffer->setInitialTarget(target);
return true;
}
bool WebGL2RenderingContextBase::validateBufferBaseTarget(
const char* functionName,
GLenum target) {
switch (target) {
case GL_TRANSFORM_FEEDBACK_BUFFER:
case GL_UNIFORM_BUFFER:
return true;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return false;
}
}
bool WebGL2RenderingContextBase::validateAndUpdateBufferBindBaseTarget(
const char* functionName,
GLenum target,
GLuint index,
WebGLBuffer* buffer) {
if (!validateBufferBaseTarget(functionName, target))
return false;
if (buffer &&
!validateBufferTargetCompatibility(functionName, target, buffer))
return false;
switch (target) {
case GL_TRANSFORM_FEEDBACK_BUFFER:
if (index >= m_boundIndexedTransformFeedbackBuffers.size()) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "index out of range");
return false;
}
m_boundIndexedTransformFeedbackBuffers[index] = buffer;
m_boundTransformFeedbackBuffer = buffer;
break;
case GL_UNIFORM_BUFFER:
if (index >= m_boundIndexedUniformBuffers.size()) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "index out of range");
return false;
}
m_boundIndexedUniformBuffers[index] = buffer;
m_boundUniformBuffer = buffer;
// Keep track of what the maximum bound uniform buffer index is
if (buffer) {
if (index > m_maxBoundUniformBufferIndex)
m_maxBoundUniformBufferIndex = index;
} else if (m_maxBoundUniformBufferIndex > 0 &&
index == m_maxBoundUniformBufferIndex) {
size_t i = m_maxBoundUniformBufferIndex - 1;
for (; i > 0; --i) {
if (m_boundIndexedUniformBuffers[i].get())
break;
}
m_maxBoundUniformBufferIndex = i;
}
break;
default:
ASSERT_NOT_REACHED();
break;
}
if (buffer && !buffer->getInitialTarget())
buffer->setInitialTarget(target);
return true;
}
bool WebGL2RenderingContextBase::validateFramebufferTarget(GLenum target) {
switch (target) {
case GL_FRAMEBUFFER:
case GL_READ_FRAMEBUFFER:
case GL_DRAW_FRAMEBUFFER:
return true;
default:
return false;
}
}
bool WebGL2RenderingContextBase::validateReadPixelsFormatAndType(
GLenum format,
GLenum type,
DOMArrayBufferView* buffer) {
switch (format) {
case GL_RED:
case GL_RED_INTEGER:
case GL_RG:
case GL_RG_INTEGER:
case GL_RGB:
case GL_RGB_INTEGER:
case GL_RGBA:
case GL_RGBA_INTEGER:
case GL_LUMINANCE_ALPHA:
case GL_LUMINANCE:
case GL_ALPHA:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "readPixels", "invalid format");
return false;
}
switch (type) {
case GL_UNSIGNED_BYTE:
if (buffer && buffer->type() != DOMArrayBufferView::TypeUint8) {
synthesizeGLError(
GL_INVALID_OPERATION, "readPixels",
"type UNSIGNED_BYTE but ArrayBufferView not Uint8Array");
return false;
}
return true;
case GL_BYTE:
if (buffer && buffer->type() != DOMArrayBufferView::TypeInt8) {
synthesizeGLError(GL_INVALID_OPERATION, "readPixels",
"type BYTE but ArrayBufferView not Int8Array");
return false;
}
return true;
case GL_HALF_FLOAT:
if (buffer && buffer->type() != DOMArrayBufferView::TypeUint16) {
synthesizeGLError(
GL_INVALID_OPERATION, "readPixels",
"type HALF_FLOAT but ArrayBufferView not Uint16Array");
return false;
}
return true;
case GL_FLOAT:
if (buffer && buffer->type() != DOMArrayBufferView::TypeFloat32) {
synthesizeGLError(GL_INVALID_OPERATION, "readPixels",
"type FLOAT but ArrayBufferView not Float32Array");
return false;
}
return true;
case GL_UNSIGNED_SHORT:
case GL_UNSIGNED_SHORT_5_6_5:
case GL_UNSIGNED_SHORT_4_4_4_4:
case GL_UNSIGNED_SHORT_5_5_5_1:
if (buffer && buffer->type() != DOMArrayBufferView::TypeUint16) {
synthesizeGLError(
GL_INVALID_OPERATION, "readPixels",
"type UNSIGNED_SHORT but ArrayBufferView not Uint16Array");
return false;
}
return true;
case GL_SHORT:
if (buffer && buffer->type() != DOMArrayBufferView::TypeInt16) {
synthesizeGLError(GL_INVALID_OPERATION, "readPixels",
"type SHORT but ArrayBufferView not Int16Array");
return false;
}
return true;
case GL_UNSIGNED_INT:
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:
if (buffer && buffer->type() != DOMArrayBufferView::TypeUint32) {
synthesizeGLError(
GL_INVALID_OPERATION, "readPixels",
"type UNSIGNED_INT but ArrayBufferView not Uint32Array");
return false;
}
return true;
case GL_INT:
if (buffer && buffer->type() != DOMArrayBufferView::TypeInt32) {
synthesizeGLError(GL_INVALID_OPERATION, "readPixels",
"type INT but ArrayBufferView not Int32Array");
return false;
}
return true;
default:
synthesizeGLError(GL_INVALID_ENUM, "readPixels", "invalid type");
return false;
}
}
WebGLFramebuffer* WebGL2RenderingContextBase::getFramebufferBinding(
GLenum target) {
switch (target) {
case GL_READ_FRAMEBUFFER:
return m_readFramebufferBinding.get();
case GL_DRAW_FRAMEBUFFER:
return m_framebufferBinding.get();
default:
return WebGLRenderingContextBase::getFramebufferBinding(target);
}
}
WebGLFramebuffer* WebGL2RenderingContextBase::getReadFramebufferBinding() {
return m_readFramebufferBinding.get();
}
bool WebGL2RenderingContextBase::validateGetFramebufferAttachmentParameterFunc(
const char* functionName,
GLenum target,
GLenum attachment) {
if (!validateFramebufferTarget(target)) {
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return false;
}
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
ASSERT(framebufferBinding || drawingBuffer());
if (!framebufferBinding) {
// for the default framebuffer
switch (attachment) {
case GL_BACK:
case GL_DEPTH:
case GL_STENCIL:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid attachment");
return false;
}
} else {
// for the FBO
switch (attachment) {
case GL_COLOR_ATTACHMENT0:
case GL_DEPTH_ATTACHMENT:
case GL_STENCIL_ATTACHMENT:
break;
case GL_DEPTH_STENCIL_ATTACHMENT:
if (framebufferBinding->getAttachmentObject(GL_DEPTH_ATTACHMENT) !=
framebufferBinding->getAttachmentObject(GL_STENCIL_ATTACHMENT)) {
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"different objects are bound to the depth and "
"stencil attachment points");
return false;
}
break;
default:
if (attachment > GL_COLOR_ATTACHMENT0 &&
attachment < static_cast<GLenum>(GL_COLOR_ATTACHMENT0 +
maxColorAttachments()))
break;
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid attachment");
return false;
}
}
return true;
}
ScriptValue WebGL2RenderingContextBase::getFramebufferAttachmentParameter(
ScriptState* scriptState,
GLenum target,
GLenum attachment,
GLenum pname) {
const char kFunctionName[] = "getFramebufferAttachmentParameter";
if (isContextLost() ||
!validateGetFramebufferAttachmentParameterFunc(kFunctionName, target,
attachment))
return ScriptValue::createNull(scriptState);
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
ASSERT(!framebufferBinding || framebufferBinding->object());
// Default framebuffer (an internal fbo)
if (!framebufferBinding) {
// We can use creationAttributes() because in WebGL 2, they are required to
// be honored.
bool hasDepth = creationAttributes().depth();
bool hasStencil = creationAttributes().stencil();
bool hasAlpha = creationAttributes().alpha();
bool missingImage = (attachment == GL_DEPTH && !hasDepth) ||
(attachment == GL_STENCIL && !hasStencil);
if (missingImage) {
switch (pname) {
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return WebGLAny(scriptState, GL_NONE);
default:
synthesizeGLError(GL_INVALID_OPERATION, kFunctionName,
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
switch (pname) {
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return WebGLAny(scriptState, GL_FRAMEBUFFER_DEFAULT);
case GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: {
GLint value = attachment == GL_BACK ? 8 : 0;
return WebGLAny(scriptState, value);
}
case GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: {
GLint value = (attachment == GL_BACK && hasAlpha) ? 8 : 0;
return WebGLAny(scriptState, value);
}
case GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: {
// For ES3 capable backend, DEPTH24_STENCIL8 has to be supported.
GLint value = attachment == GL_DEPTH ? 24 : 0;
return WebGLAny(scriptState, value);
}
case GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: {
GLint value = attachment == GL_STENCIL ? 8 : 0;
return WebGLAny(scriptState, value);
}
case GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
return WebGLAny(scriptState, GL_UNSIGNED_NORMALIZED);
case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
return WebGLAny(scriptState, GL_LINEAR);
default:
synthesizeGLError(GL_INVALID_ENUM, kFunctionName,
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
WebGLSharedObject* attachmentObject = nullptr;
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
WebGLSharedObject* depthAttachment =
framebufferBinding->getAttachmentObject(GL_DEPTH_ATTACHMENT);
WebGLSharedObject* stencilAttachment =
framebufferBinding->getAttachmentObject(GL_STENCIL_ATTACHMENT);
if (depthAttachment != stencilAttachment) {
synthesizeGLError(
GL_INVALID_OPERATION, kFunctionName,
"different objects bound to DEPTH_ATTACHMENT and STENCIL_ATTACHMENT");
return ScriptValue::createNull(scriptState);
}
attachmentObject = depthAttachment;
} else {
attachmentObject = framebufferBinding->getAttachmentObject(attachment);
}
if (!attachmentObject) {
switch (pname) {
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return WebGLAny(scriptState, GL_NONE);
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
return ScriptValue::createNull(scriptState);
default:
synthesizeGLError(GL_INVALID_OPERATION, kFunctionName,
"invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
ASSERT(attachmentObject->isTexture() || attachmentObject->isRenderbuffer());
switch (pname) {
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
if (attachmentObject->isTexture())
return WebGLAny(scriptState, GL_TEXTURE);
return WebGLAny(scriptState, GL_RENDERBUFFER);
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
return WebGLAny(scriptState, attachmentObject);
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER:
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:
if (!attachmentObject->isTexture())
break;
case GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
case GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: {
GLint value = 0;
contextGL()->GetFramebufferAttachmentParameteriv(target, attachment,
pname, &value);
return WebGLAny(scriptState, value);
}
case GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
synthesizeGLError(
GL_INVALID_OPERATION, kFunctionName,
"COMPONENT_TYPE can't be queried for DEPTH_STENCIL_ATTACHMENT");
return ScriptValue::createNull(scriptState);
}
case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: {
GLint value = 0;
contextGL()->GetFramebufferAttachmentParameteriv(target, attachment,
pname, &value);
return WebGLAny(scriptState, static_cast<unsigned>(value));
}
default:
break;
}
synthesizeGLError(GL_INVALID_ENUM, kFunctionName, "invalid parameter name");
return ScriptValue::createNull(scriptState);
}
DEFINE_TRACE(WebGL2RenderingContextBase) {
visitor->trace(m_readFramebufferBinding);
visitor->trace(m_transformFeedbackBinding);
visitor->trace(m_boundCopyReadBuffer);
visitor->trace(m_boundCopyWriteBuffer);
visitor->trace(m_boundPixelPackBuffer);
visitor->trace(m_boundPixelUnpackBuffer);
visitor->trace(m_boundTransformFeedbackBuffer);
visitor->trace(m_boundUniformBuffer);
visitor->trace(m_boundIndexedTransformFeedbackBuffers);
visitor->trace(m_boundIndexedUniformBuffers);
visitor->trace(m_currentBooleanOcclusionQuery);
visitor->trace(m_currentTransformFeedbackPrimitivesWrittenQuery);
visitor->trace(m_samplerUnits);
WebGLRenderingContextBase::trace(visitor);
}
WebGLTexture* WebGL2RenderingContextBase::validateTexture3DBinding(
const char* functionName,
GLenum target) {
WebGLTexture* tex = nullptr;
switch (target) {
case GL_TEXTURE_2D_ARRAY:
tex = m_textureUnits[m_activeTextureUnit].m_texture2DArrayBinding.get();
break;
case GL_TEXTURE_3D:
tex = m_textureUnits[m_activeTextureUnit].m_texture3DBinding.get();
break;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName,
"invalid texture target");
return nullptr;
}
if (!tex)
synthesizeGLError(GL_INVALID_OPERATION, functionName,
"no texture bound to target");
return tex;
}
GLint WebGL2RenderingContextBase::getMaxTextureLevelForTarget(GLenum target) {
switch (target) {
case GL_TEXTURE_3D:
return m_max3DTextureLevel;
case GL_TEXTURE_2D_ARRAY:
return m_maxTextureLevel;
}
return WebGLRenderingContextBase::getMaxTextureLevelForTarget(target);
}
ScriptValue WebGL2RenderingContextBase::getTexParameter(
ScriptState* scriptState,
GLenum target,
GLenum pname) {
if (isContextLost() || !validateTextureBinding("getTexParameter", target))
return ScriptValue::createNull(scriptState);
switch (pname) {
case GL_TEXTURE_WRAP_R:
case GL_TEXTURE_COMPARE_FUNC:
case GL_TEXTURE_COMPARE_MODE:
case GL_TEXTURE_IMMUTABLE_LEVELS: {
GLint value = 0;
contextGL()->GetTexParameteriv(target, pname, &value);
return WebGLAny(scriptState, static_cast<unsigned>(value));
}
case GL_TEXTURE_IMMUTABLE_FORMAT: {
GLint value = 0;
contextGL()->GetTexParameteriv(target, pname, &value);
return WebGLAny(scriptState, static_cast<bool>(value));
}
case GL_TEXTURE_BASE_LEVEL:
case GL_TEXTURE_MAX_LEVEL: {
GLint value = 0;
contextGL()->GetTexParameteriv(target, pname, &value);
return WebGLAny(scriptState, value);
}
case GL_TEXTURE_MAX_LOD:
case GL_TEXTURE_MIN_LOD: {
GLfloat value = 0.f;
contextGL()->GetTexParameterfv(target, pname, &value);
return WebGLAny(scriptState, value);
}
default:
return WebGLRenderingContextBase::getTexParameter(scriptState, target,
pname);
}
}
WebGLBuffer* WebGL2RenderingContextBase::validateBufferDataTarget(
const char* functionName,
GLenum target) {
WebGLBuffer* buffer = nullptr;
switch (target) {
case GL_ELEMENT_ARRAY_BUFFER:
buffer = m_boundVertexArrayObject->boundElementArrayBuffer();
break;
case GL_ARRAY_BUFFER:
buffer = m_boundArrayBuffer.get();
break;
case GL_COPY_READ_BUFFER:
buffer = m_boundCopyReadBuffer.get();
break;
case GL_COPY_WRITE_BUFFER:
buffer = m_boundCopyWriteBuffer.get();
break;
case GL_PIXEL_PACK_BUFFER:
buffer = m_boundPixelPackBuffer.get();
break;
case GL_PIXEL_UNPACK_BUFFER:
buffer = m_boundPixelUnpackBuffer.get();
break;
case GL_TRANSFORM_FEEDBACK_BUFFER:
buffer = m_boundTransformFeedbackBuffer.get();
break;
case GL_UNIFORM_BUFFER:
buffer = m_boundUniformBuffer.get();
break;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return nullptr;
}
if (!buffer) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "no buffer");
return nullptr;
}
return buffer;
}
bool WebGL2RenderingContextBase::validateBufferDataUsage(
const char* functionName,
GLenum usage) {
switch (usage) {
case GL_STREAM_READ:
case GL_STREAM_COPY:
case GL_STATIC_READ:
case GL_STATIC_COPY:
case GL_DYNAMIC_READ:
case GL_DYNAMIC_COPY:
return true;
default:
return WebGLRenderingContextBase::validateBufferDataUsage(functionName,
usage);
}
}
void WebGL2RenderingContextBase::removeBoundBuffer(WebGLBuffer* buffer) {
if (m_boundCopyReadBuffer == buffer)
m_boundCopyReadBuffer = nullptr;
if (m_boundCopyWriteBuffer == buffer)
m_boundCopyWriteBuffer = nullptr;
if (m_boundPixelPackBuffer == buffer)
m_boundPixelPackBuffer = nullptr;
if (m_boundPixelUnpackBuffer == buffer)
m_boundPixelUnpackBuffer = nullptr;
if (m_boundTransformFeedbackBuffer == buffer)
m_boundTransformFeedbackBuffer = nullptr;
if (m_boundUniformBuffer == buffer)
m_boundUniformBuffer = nullptr;
WebGLRenderingContextBase::removeBoundBuffer(buffer);
}
void WebGL2RenderingContextBase::restoreCurrentFramebuffer() {
bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebufferBinding.get());
bindFramebuffer(GL_READ_FRAMEBUFFER, m_readFramebufferBinding.get());
}
void WebGL2RenderingContextBase::visitChildDOMWrappers(
v8::Isolate* isolate,
const v8::Persistent<v8::Object>& wrapper) {
if (isContextLost()) {
return;
}
WebGLRenderingContextBase::visitChildDOMWrappers(isolate, wrapper);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_transformFeedbackBinding, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_readFramebufferBinding, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_boundCopyReadBuffer, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_boundCopyWriteBuffer, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_boundPixelPackBuffer, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_boundPixelUnpackBuffer, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_boundTransformFeedbackBuffer, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_boundUniformBuffer, isolate);
for (auto& buf : m_boundIndexedTransformFeedbackBuffers) {
DOMWrapperWorld::setWrapperReferencesInAllWorlds(wrapper, buf, isolate);
}
for (auto& buf : m_boundIndexedUniformBuffers) {
DOMWrapperWorld::setWrapperReferencesInAllWorlds(wrapper, buf, isolate);
}
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_currentBooleanOcclusionQuery, isolate);
DOMWrapperWorld::setWrapperReferencesInAllWorlds(
wrapper, m_currentTransformFeedbackPrimitivesWrittenQuery, isolate);
for (auto& unit : m_samplerUnits) {
DOMWrapperWorld::setWrapperReferencesInAllWorlds(wrapper, unit, isolate);
}
}
WebGLImageConversion::PixelStoreParams
WebGL2RenderingContextBase::getPackPixelStoreParams() {
WebGLImageConversion::PixelStoreParams params;
params.alignment = m_packAlignment;
params.rowLength = m_packRowLength;
params.skipPixels = m_packSkipPixels;
params.skipRows = m_packSkipRows;
return params;
}
WebGLImageConversion::PixelStoreParams
WebGL2RenderingContextBase::getUnpackPixelStoreParams(
TexImageDimension dimension) {
WebGLImageConversion::PixelStoreParams params;
params.alignment = m_unpackAlignment;
params.rowLength = m_unpackRowLength;
params.skipPixels = m_unpackSkipPixels;
params.skipRows = m_unpackSkipRows;
if (dimension == Tex3D) {
params.imageHeight = m_unpackImageHeight;
params.skipImages = m_unpackSkipImages;
}
return params;
}
} // namespace blink