blob: cb747f8aed20efce9c18742e248ae438cffcdc54 [file] [log] [blame]
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "modules/webgl/WebGLRenderingContextBase.h"
#include "bindings/core/v8/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/modules/v8/WebGLAny.h"
#include "core/dom/DOMArrayBuffer.h"
#include "core/dom/DOMTypedArray.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/FlexibleArrayBufferView.h"
#include "core/fetch/ImageResource.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLVideoElement.h"
#include "core/html/ImageData.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/layout/LayoutBox.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h"
#include "modules/webgl/ANGLEInstancedArrays.h"
#include "modules/webgl/CHROMIUMSubscribeUniform.h"
#include "modules/webgl/CHROMIUMValuebuffer.h"
#include "modules/webgl/EXTBlendMinMax.h"
#include "modules/webgl/EXTFragDepth.h"
#include "modules/webgl/EXTShaderTextureLOD.h"
#include "modules/webgl/EXTTextureFilterAnisotropic.h"
#include "modules/webgl/OESElementIndexUint.h"
#include "modules/webgl/OESStandardDerivatives.h"
#include "modules/webgl/OESTextureFloat.h"
#include "modules/webgl/OESTextureFloatLinear.h"
#include "modules/webgl/OESTextureHalfFloat.h"
#include "modules/webgl/OESTextureHalfFloatLinear.h"
#include "modules/webgl/OESVertexArrayObject.h"
#include "modules/webgl/WebGLActiveInfo.h"
#include "modules/webgl/WebGLBuffer.h"
#include "modules/webgl/WebGLCompressedTextureASTC.h"
#include "modules/webgl/WebGLCompressedTextureATC.h"
#include "modules/webgl/WebGLCompressedTextureETC1.h"
#include "modules/webgl/WebGLCompressedTexturePVRTC.h"
#include "modules/webgl/WebGLCompressedTextureS3TC.h"
#include "modules/webgl/WebGLContextAttributeHelpers.h"
#include "modules/webgl/WebGLContextAttributes.h"
#include "modules/webgl/WebGLContextEvent.h"
#include "modules/webgl/WebGLContextGroup.h"
#include "modules/webgl/WebGLDebugRendererInfo.h"
#include "modules/webgl/WebGLDebugShaders.h"
#include "modules/webgl/WebGLDepthTexture.h"
#include "modules/webgl/WebGLDrawBuffers.h"
#include "modules/webgl/WebGLFramebuffer.h"
#include "modules/webgl/WebGLLoseContext.h"
#include "modules/webgl/WebGLProgram.h"
#include "modules/webgl/WebGLRenderbuffer.h"
#include "modules/webgl/WebGLShader.h"
#include "modules/webgl/WebGLShaderPrecisionFormat.h"
#include "modules/webgl/WebGLTexture.h"
#include "modules/webgl/WebGLUniformLocation.h"
#include "modules/webgl/WebGLVertexArrayObject.h"
#include "modules/webgl/WebGLVertexArrayObjectOES.h"
#include "platform/CheckedInt.h"
#include "platform/NotImplemented.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/IntSize.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/UnacceleratedImageBufferSurface.h"
#include "platform/graphics/gpu/AcceleratedImageBufferSurface.h"
#include "platform/graphics/gpu/DrawingBuffer.h"
#include "public/platform/Platform.h"
#include "wtf/ArrayBufferContents.h"
#include "wtf/PassOwnPtr.h"
#include "wtf/text/StringBuilder.h"
namespace blink {
namespace {
const double secondsBetweenRestoreAttempts = 1.0;
const int maxGLErrorsAllowedToConsole = 256;
const unsigned maxGLActiveContexts = 16;
} // namespace
// FIXME: Oilpan: static vectors to heap allocated WebGLRenderingContextBase objects
// are kept here. This relies on the WebGLRenderingContextBase finalization to
// explicitly retire themselves from these vectors, but it'd be preferable if
// the references were traced as per usual.
Vector<WebGLRenderingContextBase*>& WebGLRenderingContextBase::activeContexts()
{
DEFINE_STATIC_LOCAL(Vector<WebGLRenderingContextBase*>, activeContexts, ());
return activeContexts;
}
Vector<WebGLRenderingContextBase*>& WebGLRenderingContextBase::forciblyEvictedContexts()
{
DEFINE_STATIC_LOCAL(Vector<WebGLRenderingContextBase*>, forciblyEvictedContexts, ());
return forciblyEvictedContexts;
}
void WebGLRenderingContextBase::forciblyLoseOldestContext(const String& reason)
{
size_t candidateID = oldestContextIndex();
if (candidateID >= activeContexts().size())
return;
WebGLRenderingContextBase* candidate = activeContexts()[candidateID];
// This context could belong to a dead page and the last JavaScript reference has already
// been lost. Garbage collection might be triggered in the middle of this function, for
// example, printWarningToConsole() causes an upcall to JavaScript.
// Must make sure that the context is not deleted until the call stack unwinds.
RefPtrWillBeRawPtr<WebGLRenderingContextBase> protect(candidate);
candidate->printWarningToConsole(reason);
InspectorInstrumentation::didFireWebGLWarning(candidate->canvas());
// This will call deactivateContext once the context has actually been lost.
candidate->forceLostContext(WebGLRenderingContextBase::SyntheticLostContext, WebGLRenderingContextBase::WhenAvailable);
}
size_t WebGLRenderingContextBase::oldestContextIndex()
{
if (!activeContexts().size())
return maxGLActiveContexts;
WebGLRenderingContextBase* candidate = activeContexts().first();
ASSERT(!candidate->isContextLost());
size_t candidateID = 0;
for (size_t ii = 1; ii < activeContexts().size(); ++ii) {
WebGLRenderingContextBase* context = activeContexts()[ii];
ASSERT(!context->isContextLost());
if (context->webContext()->lastFlushID() < candidate->webContext()->lastFlushID()) {
candidate = context;
candidateID = ii;
}
}
return candidateID;
}
void WebGLRenderingContextBase::activateContext(WebGLRenderingContextBase* context)
{
unsigned removedContexts = 0;
while (activeContexts().size() >= maxGLActiveContexts && removedContexts < maxGLActiveContexts) {
forciblyLoseOldestContext("WARNING: Too many active WebGL contexts. Oldest context will be lost.");
removedContexts++;
}
ASSERT(!context->isContextLost());
if (!activeContexts().contains(context))
activeContexts().append(context);
}
void WebGLRenderingContextBase::deactivateContext(WebGLRenderingContextBase* context)
{
size_t position = activeContexts().find(context);
if (position != WTF::kNotFound)
activeContexts().remove(position);
}
void WebGLRenderingContextBase::addToEvictedList(WebGLRenderingContextBase* context)
{
if (!forciblyEvictedContexts().contains(context))
forciblyEvictedContexts().append(context);
}
void WebGLRenderingContextBase::removeFromEvictedList(WebGLRenderingContextBase* context)
{
size_t position = forciblyEvictedContexts().find(context);
if (position != WTF::kNotFound)
forciblyEvictedContexts().remove(position);
}
void WebGLRenderingContextBase::willDestroyContext(WebGLRenderingContextBase* context)
{
removeFromEvictedList(context);
deactivateContext(context);
// Try to re-enable the oldest inactive contexts.
while(activeContexts().size() < maxGLActiveContexts && forciblyEvictedContexts().size()) {
WebGLRenderingContextBase* evictedContext = forciblyEvictedContexts().first();
if (!evictedContext->m_restoreAllowed) {
forciblyEvictedContexts().remove(0);
continue;
}
IntSize desiredSize = DrawingBuffer::adjustSize(evictedContext->clampedCanvasSize(), IntSize(), evictedContext->m_maxTextureSize);
// If there's room in the pixel budget for this context, restore it.
if (!desiredSize.isEmpty()) {
forciblyEvictedContexts().remove(0);
evictedContext->forceRestoreContext();
}
break;
}
}
namespace {
GLint clamp(GLint value, GLint min, GLint max)
{
if (value < min)
value = min;
if (value > max)
value = max;
return value;
}
// Return true if a character belongs to the ASCII subset as defined in
// GLSL ES 1.0 spec section 3.1.
bool validateCharacter(unsigned char c)
{
// Printing characters are valid except " $ ` @ \ ' DEL.
if (c >= 32 && c <= 126
&& c != '"' && c != '$' && c != '`' && c != '@' && c != '\\' && c != '\'')
return true;
// Horizontal tab, line feed, vertical tab, form feed, carriage return
// are also valid.
if (c >= 9 && c <= 13)
return true;
return false;
}
bool isPrefixReserved(const String& name)
{
if (name.startsWith("gl_") || name.startsWith("webgl_") || name.startsWith("_webgl_"))
return true;
return false;
}
// Strips comments from shader text. This allows non-ASCII characters
// to be used in comments without potentially breaking OpenGL
// implementations not expecting characters outside the GLSL ES set.
class StripComments {
public:
StripComments(const String& str)
: m_parseState(BeginningOfLine)
, m_sourceString(str)
, m_length(str.length())
, m_position(0)
{
parse();
}
String result()
{
return m_builder.toString();
}
private:
bool hasMoreCharacters() const
{
return (m_position < m_length);
}
void parse()
{
while (hasMoreCharacters()) {
process(current());
// process() might advance the position.
if (hasMoreCharacters())
advance();
}
}
void process(UChar);
bool peek(UChar& character) const
{
if (m_position + 1 >= m_length)
return false;
character = m_sourceString[m_position + 1];
return true;
}
UChar current()
{
ASSERT_WITH_SECURITY_IMPLICATION(m_position < m_length);
return m_sourceString[m_position];
}
void advance()
{
++m_position;
}
static bool isNewline(UChar character)
{
// Don't attempt to canonicalize newline related characters.
return (character == '\n' || character == '\r');
}
void emit(UChar character)
{
m_builder.append(character);
}
enum ParseState {
// Have not seen an ASCII non-whitespace character yet on
// this line. Possible that we might see a preprocessor
// directive.
BeginningOfLine,
// Have seen at least one ASCII non-whitespace character
// on this line.
MiddleOfLine,
// Handling a preprocessor directive. Passes through all
// characters up to the end of the line. Disables comment
// processing.
InPreprocessorDirective,
// Handling a single-line comment. The comment text is
// replaced with a single space.
InSingleLineComment,
// Handling a multi-line comment. Newlines are passed
// through to preserve line numbers.
InMultiLineComment
};
ParseState m_parseState;
String m_sourceString;
unsigned m_length;
unsigned m_position;
StringBuilder m_builder;
};
void StripComments::process(UChar c)
{
if (isNewline(c)) {
// No matter what state we are in, pass through newlines
// so we preserve line numbers.
emit(c);
if (m_parseState != InMultiLineComment)
m_parseState = BeginningOfLine;
return;
}
UChar temp = 0;
switch (m_parseState) {
case BeginningOfLine:
if (WTF::isASCIISpace(c)) {
emit(c);
break;
}
if (c == '#') {
m_parseState = InPreprocessorDirective;
emit(c);
break;
}
// Transition to normal state and re-handle character.
m_parseState = MiddleOfLine;
process(c);
break;
case MiddleOfLine:
if (c == '/' && peek(temp)) {
if (temp == '/') {
m_parseState = InSingleLineComment;
emit(' ');
advance();
break;
}
if (temp == '*') {
m_parseState = InMultiLineComment;
// Emit the comment start in case the user has
// an unclosed comment and we want to later
// signal an error.
emit('/');
emit('*');
advance();
break;
}
}
emit(c);
break;
case InPreprocessorDirective:
// No matter what the character is, just pass it
// through. Do not parse comments in this state. This
// might not be the right thing to do long term, but it
// should handle the #error preprocessor directive.
emit(c);
break;
case InSingleLineComment:
// The newline code at the top of this function takes care
// of resetting our state when we get out of the
// single-line comment. Swallow all other characters.
break;
case InMultiLineComment:
if (c == '*' && peek(temp) && temp == '/') {
emit('*');
emit('/');
m_parseState = MiddleOfLine;
advance();
break;
}
// Swallow all other characters. Unclear whether we may
// want or need to just emit a space per character to try
// to preserve column numbers for debugging purposes.
break;
}
}
static bool shouldFailContextCreationForTesting = false;
} // namespace anonymous
class ScopedTexture2DRestorer {
STACK_ALLOCATED();
public:
explicit ScopedTexture2DRestorer(WebGLRenderingContextBase* context)
: m_context(context)
{
}
~ScopedTexture2DRestorer()
{
m_context->restoreCurrentTexture2D();
}
private:
RawPtrWillBeMember<WebGLRenderingContextBase> m_context;
};
class ScopedFramebufferRestorer {
STACK_ALLOCATED();
public:
explicit ScopedFramebufferRestorer(WebGLRenderingContextBase* context)
: m_context(context)
{
}
~ScopedFramebufferRestorer()
{
m_context->restoreCurrentFramebuffer();
}
private:
RawPtrWillBeMember<WebGLRenderingContextBase> m_context;
};
class WebGLRenderingContextLostCallback final : public GarbageCollectedFinalized<WebGLRenderingContextLostCallback>, public WebGraphicsContext3D::WebGraphicsContextLostCallback {
public:
static WebGLRenderingContextLostCallback* create(WebGLRenderingContextBase* context)
{
return new WebGLRenderingContextLostCallback(context);
}
~WebGLRenderingContextLostCallback() override { }
virtual void onContextLost() { m_context->forceLostContext(WebGLRenderingContextBase::RealLostContext, WebGLRenderingContextBase::Auto); }
DEFINE_INLINE_TRACE()
{
visitor->trace(m_context);
}
private:
explicit WebGLRenderingContextLostCallback(WebGLRenderingContextBase* context)
: m_context(context) { }
RawPtrWillBeMember<WebGLRenderingContextBase> m_context;
};
class WebGLRenderingContextErrorMessageCallback final : public GarbageCollectedFinalized<WebGLRenderingContextErrorMessageCallback>, public WebGraphicsContext3D::WebGraphicsErrorMessageCallback {
public:
static WebGLRenderingContextErrorMessageCallback* create(WebGLRenderingContextBase* context)
{
return new WebGLRenderingContextErrorMessageCallback(context);
}
~WebGLRenderingContextErrorMessageCallback() override { }
virtual void onErrorMessage(const WebString& message, WGC3Dint)
{
if (m_context->m_synthesizedErrorsToConsole)
m_context->printGLErrorToConsole(message);
InspectorInstrumentation::didFireWebGLErrorOrWarning(m_context->canvas(), message);
}
DEFINE_INLINE_TRACE()
{
visitor->trace(m_context);
}
private:
explicit WebGLRenderingContextErrorMessageCallback(WebGLRenderingContextBase* context)
: m_context(context) { }
RawPtrWillBeMember<WebGLRenderingContextBase> m_context;
};
PassOwnPtr<WebGraphicsContext3D> WebGLRenderingContextBase::createWebGraphicsContext3D(HTMLCanvasElement* canvas, WebGLContextAttributes attributes, unsigned webGLVersion)
{
Document& document = canvas->document();
LocalFrame* frame = document.frame();
if (!frame) {
canvas->dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Web page was not allowed to create a WebGL context."));
return nullptr;
}
Settings* settings = frame->settings();
// The FrameLoaderClient might block creation of a new WebGL context despite the page settings; in
// particular, if WebGL contexts were lost one or more times via the GL_ARB_robustness extension.
if (!frame->loader().client()->allowWebGL(settings && settings->webGLEnabled())) {
canvas->dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Web page was not allowed to create a WebGL context."));
return nullptr;
}
WebGraphicsContext3D::Attributes wgc3dAttributes = toWebGraphicsContext3DAttributes(attributes, document.topDocument().url().string(), settings, webGLVersion);
WebGLInfo glInfo;
OwnPtr<WebGraphicsContext3D> context = adoptPtr(Platform::current()->createOffscreenGraphicsContext3D(wgc3dAttributes, 0, &glInfo));
if (!context || shouldFailContextCreationForTesting) {
shouldFailContextCreationForTesting = false;
String statusMessage;
if (!glInfo.contextInfoCollectionFailure.isEmpty()) {
statusMessage.append("Could not create a WebGL context. ");
statusMessage.append(glInfo.contextInfoCollectionFailure);
String vendorId = String::number(glInfo.vendorId);
String deviceId = String::number(glInfo.deviceId);
if (vendorId.isEmpty())
statusMessage.append("VendorId = Not Available");
else
statusMessage.append("VendorId = " + vendorId);
if (deviceId.isEmpty())
statusMessage.append(", DeviceId = Not Available");
else
statusMessage.append(", DeviceId = " + deviceId);
} else {
statusMessage.append("Could not create a WebGL context");
if (!glInfo.vendorInfo.isEmpty()) {
statusMessage.append(", VendorInfo = ");
statusMessage.append(glInfo.vendorInfo);
} else {
statusMessage.append(", VendorInfo = Not Available");
}
if (!glInfo.rendererInfo.isEmpty()) {
statusMessage.append(", RendererInfo = ");
statusMessage.append(glInfo.rendererInfo);
} else {
statusMessage.append(", RendererInfo = Not Available");
}
if (!glInfo.driverVersion.isEmpty()) {
statusMessage.append(", DriverInfo = ");
statusMessage.append(glInfo.driverVersion);
} else {
statusMessage.append(", DriverInfo = Not Available");
}
statusMessage.append(".");
}
canvas->dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, statusMessage));
return nullptr;
}
return context.release();
}
void WebGLRenderingContextBase::forceNextWebGLContextCreationToFail()
{
shouldFailContextCreationForTesting = true;
}
namespace {
// ES2 enums
static const GLenum kSupportedInternalFormatsES2[] = {
GL_RGB,
GL_RGBA,
GL_LUMINANCE_ALPHA,
GL_LUMINANCE,
GL_ALPHA,
};
// Exposed by GL_ANGLE_depth_texture
static const GLenum kSupportedInternalFormatsOESDepthTex[] = {
GL_DEPTH_COMPONENT,
GL_DEPTH_STENCIL,
};
// Exposed by GL_EXT_sRGB
static const GLenum kSupportedInternalFormatsEXTsRGB[] = {
GL_SRGB,
GL_SRGB_ALPHA_EXT,
};
// ES3 enums
static const GLenum kSupportedInternalFormatsES3[] = {
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_RGBA32I,
GL_RGBA32UI,
GL_DEPTH_COMPONENT16,
GL_DEPTH_COMPONENT24,
GL_DEPTH_COMPONENT32F,
GL_DEPTH24_STENCIL8,
};
// ES2 enums
static const GLenum kSupportedFormatsES2[] = {
GL_RGB,
GL_RGBA,
GL_LUMINANCE_ALPHA,
GL_LUMINANCE,
GL_ALPHA,
};
// Exposed by GL_ANGLE_depth_texture
static const GLenum kSupportedFormatsOESDepthTex[] = {
GL_DEPTH_COMPONENT,
GL_DEPTH_STENCIL,
};
// Exposed by GL_EXT_sRGB
static const GLenum kSupportedFormatsEXTsRGB[] = {
GL_SRGB,
GL_SRGB_ALPHA_EXT,
};
// ES3 enums
static const GLenum kSupportedFormatsES3[] = {
GL_RED,
GL_RED_INTEGER,
GL_RG,
GL_RG_INTEGER,
GL_RGB,
GL_RGB_INTEGER,
GL_RGBA,
GL_RGBA_INTEGER,
GL_DEPTH_COMPONENT,
GL_DEPTH_STENCIL,
};
// ES2 enums
static const GLenum kSupportedTypesES2[] = {
GL_UNSIGNED_BYTE,
GL_UNSIGNED_SHORT_5_6_5,
GL_UNSIGNED_SHORT_4_4_4_4,
GL_UNSIGNED_SHORT_5_5_5_1,
};
// Exposed by GL_OES_texture_float
static const GLenum kSupportedTypesOESTexFloat[] = {
GL_FLOAT,
};
// Exposed by GL_OES_texture_half_float
static const GLenum kSupportedTypesOESTexHalfFloat[] = {
GL_HALF_FLOAT_OES,
};
// Exposed by GL_ANGLE_depth_texture
static const GLenum kSupportedTypesOESDepthTex[] = {
GL_UNSIGNED_SHORT,
GL_UNSIGNED_INT,
GL_UNSIGNED_INT_24_8,
};
// ES3 enums
static const GLenum kSupportedTypesES3[] = {
GL_BYTE,
GL_UNSIGNED_SHORT,
GL_SHORT,
GL_UNSIGNED_INT,
GL_INT,
GL_HALF_FLOAT,
GL_FLOAT,
GL_UNSIGNED_INT_2_10_10_10_REV,
GL_UNSIGNED_INT_10F_11F_11F_REV,
GL_UNSIGNED_INT_5_9_9_9_REV,
GL_UNSIGNED_INT_24_8,
};
// ES2 enums
static const FormatType kSupportedFormatTypesES2[] = {
{ GL_RGB, GL_RGB, GL_UNSIGNED_BYTE },
{ GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 },
{ GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE },
{ GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 },
{ GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 },
{ GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE },
{ GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE },
{ GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE },
};
// Exposed by GL_OES_texture_float
static const FormatType kSupportedFormatTypesOESTexFloat[] = {
{ GL_RGB, GL_RGB, GL_FLOAT },
{ GL_RGBA, GL_RGBA, GL_FLOAT },
{ GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_FLOAT },
{ GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT },
{ GL_ALPHA, GL_ALPHA, GL_FLOAT },
};
// Exposed by GL_OES_texture_half_float
static const FormatType kSupportedFormatTypesOESTexHalfFloat[] = {
{ GL_RGB, GL_RGB, GL_HALF_FLOAT_OES },
{ GL_RGBA, GL_RGBA, GL_HALF_FLOAT_OES },
{ GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_OES },
{ GL_LUMINANCE, GL_LUMINANCE, GL_HALF_FLOAT_OES },
{ GL_ALPHA, GL_ALPHA, GL_HALF_FLOAT_OES },
};
// Exposed by GL_ANGLE_depth_texture
static const FormatType kSupportedFormatTypesOESDepthTex[] = {
{ GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT },
{ GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT },
{ GL_DEPTH_STENCIL, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 },
};
// Exposed by GL_EXT_sRGB
static const FormatType kSupportedFormatTypesEXTsRGB[] = {
{ GL_SRGB, GL_SRGB, GL_UNSIGNED_BYTE },
{ GL_SRGB_ALPHA_EXT, GL_SRGB_ALPHA_EXT, GL_UNSIGNED_BYTE },
};
// ES3 enums
static const FormatType kSupportedFormatTypesES3[] = {
{ GL_R8, GL_RED, GL_UNSIGNED_BYTE },
{ GL_R8_SNORM, GL_RED, GL_BYTE },
{ GL_R16F, GL_RED, GL_HALF_FLOAT },
{ GL_R16F, GL_RED, GL_FLOAT },
{ GL_R32F, GL_RED, GL_FLOAT },
{ GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE },
{ GL_R8I, GL_RED_INTEGER, GL_BYTE },
{ GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT },
{ GL_R16I, GL_RED_INTEGER, GL_SHORT },
{ GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT },
{ GL_R32I, GL_RED_INTEGER, GL_INT },
{ GL_RG8, GL_RG, GL_UNSIGNED_BYTE },
{ GL_RG8_SNORM, GL_RG, GL_BYTE },
{ GL_RG16F, GL_RG, GL_HALF_FLOAT },
{ GL_RG16F, GL_RG, GL_FLOAT },
{ GL_RG32F, GL_RG, GL_FLOAT },
{ GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE },
{ GL_RG8I, GL_RG_INTEGER, GL_BYTE },
{ GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT },
{ GL_RG16I, GL_RG_INTEGER, GL_SHORT },
{ GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT },
{ GL_RG32I, GL_RG_INTEGER, GL_INT },
{ GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE },
{ GL_SRGB8, GL_RGB, GL_UNSIGNED_BYTE },
{ GL_RGB565, GL_RGB, GL_UNSIGNED_BYTE, },
{ GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 },
{ GL_RGB8_SNORM, GL_RGB, GL_BYTE },
{ GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV },
{ GL_R11F_G11F_B10F, GL_RGB, GL_HALF_FLOAT },
{ GL_R11F_G11F_B10F, GL_RGB, GL_FLOAT },
{ GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV },
{ GL_RGB9_E5, GL_RGB, GL_HALF_FLOAT },
{ GL_RGB9_E5, GL_RGB, GL_FLOAT },
{ GL_RGB16F, GL_RGB, GL_HALF_FLOAT },
{ GL_RGB16F, GL_RGB, GL_FLOAT },
{ GL_RGB32F, GL_RGB, GL_FLOAT },
{ GL_RGB8UI, GL_RGB_INTEGER, GL_UNSIGNED_BYTE },
{ GL_RGB8I, GL_RGB_INTEGER, GL_BYTE },
{ GL_RGB16UI, GL_RGB_INTEGER, GL_UNSIGNED_SHORT },
{ GL_RGB16I, GL_RGB_INTEGER, GL_SHORT },
{ GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT },
{ GL_RGB32I, GL_RGB_INTEGER, GL_INT },
{ GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE },
{ GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE },
{ GL_RGBA8_SNORM, GL_RGBA, GL_BYTE },
{ GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_BYTE },
{ GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 },
{ GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV },
{ GL_RGBA4, GL_RGBA, GL_UNSIGNED_BYTE },
{ GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 },
{ GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV },
{ GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT },
{ GL_RGBA16F, GL_RGBA, GL_FLOAT },
{ GL_RGBA32F, GL_RGBA, GL_FLOAT },
{ GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE },
{ GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE },
{ GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV },
{ GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT },
{ GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT },
{ GL_RGBA32I, GL_RGBA_INTEGER, GL_INT },
{ GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT },
{ GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT },
{ GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT },
{ GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT },
{ GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT },
{ GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 },
};
} // namespace anonymous
#define ADD_VALUES_TO_SET(set, values) \
for (size_t i = 0; i < arraysize(values); ++i) { \
set.insert(values[i]); \
}
WebGLRenderingContextBase::WebGLRenderingContextBase(HTMLCanvasElement* passedCanvas, PassOwnPtr<WebGraphicsContext3D> context, const WebGLContextAttributes& requestedAttributes)
: CanvasRenderingContext(passedCanvas)
, m_contextLostMode(NotLostContext)
, m_autoRecoveryMethod(Manual)
, m_dispatchContextLostEventTimer(this, &WebGLRenderingContextBase::dispatchContextLostEvent)
, m_restoreAllowed(false)
, m_restoreTimer(this, &WebGLRenderingContextBase::maybeRestoreContext)
, m_generatedImageCache(4)
, m_requestedAttributes(requestedAttributes)
, m_synthesizedErrorsToConsole(true)
, m_numGLErrorsToConsoleAllowed(maxGLErrorsAllowedToConsole)
, m_multisamplingAllowed(false)
, m_multisamplingObserverRegistered(false)
, m_onePlusMaxNonDefaultTextureUnit(0)
, m_isWebGL2FormatsTypesAdded(false)
, m_isOESTextureFloatFormatsTypesAdded(false)
, m_isOESTextureHalfFloatFormatsTypesAdded(false)
, m_isWebGLDepthTextureFormatsTypesAdded(false)
, m_isEXTsRGBFormatsTypesAdded(false)
{
ASSERT(context);
m_contextGroup = WebGLContextGroup::create();
m_contextGroup->addContext(this);
m_maxViewportDims[0] = m_maxViewportDims[1] = 0;
context->getIntegerv(GL_MAX_VIEWPORT_DIMS, m_maxViewportDims);
RefPtr<DrawingBuffer> buffer = createDrawingBuffer(context);
if (!buffer) {
m_contextLostMode = SyntheticLostContext;
return;
}
m_drawingBuffer = buffer.release();
drawingBuffer()->bind(GL_FRAMEBUFFER);
setupFlags();
ADD_VALUES_TO_SET(m_supportedInternalFormats, kSupportedInternalFormatsES2);
ADD_VALUES_TO_SET(m_supportedFormats, kSupportedFormatsES2);
ADD_VALUES_TO_SET(m_supportedTypes, kSupportedTypesES2);
ADD_VALUES_TO_SET(m_supportedFormatTypeCombinations, kSupportedFormatTypesES2);
}
PassRefPtr<DrawingBuffer> WebGLRenderingContextBase::createDrawingBuffer(PassOwnPtr<WebGraphicsContext3D> context)
{
WebGraphicsContext3D::Attributes attrs;
attrs.alpha = m_requestedAttributes.alpha();
attrs.depth = m_requestedAttributes.depth();
attrs.stencil = m_requestedAttributes.stencil();
attrs.antialias = m_requestedAttributes.antialias();
attrs.premultipliedAlpha = m_requestedAttributes.premultipliedAlpha();
DrawingBuffer::PreserveDrawingBuffer preserve = m_requestedAttributes.preserveDrawingBuffer() ? DrawingBuffer::Preserve : DrawingBuffer::Discard;
return DrawingBuffer::create(context, clampedCanvasSize(), preserve, attrs);
}
void WebGLRenderingContextBase::initializeNewContext()
{
ASSERT(!isContextLost());
ASSERT(drawingBuffer());
m_markedCanvasDirty = false;
m_activeTextureUnit = 0;
m_packAlignment = 4;
m_unpackAlignment = 4;
m_unpackFlipY = false;
m_unpackPremultiplyAlpha = false;
m_unpackColorspaceConversion = GC3D_BROWSER_DEFAULT_WEBGL;
m_boundArrayBuffer = nullptr;
m_currentProgram = nullptr;
m_framebufferBinding = nullptr;
m_renderbufferBinding = nullptr;
m_valuebufferBinding = nullptr;
m_depthMask = true;
m_stencilEnabled = false;
m_stencilMask = 0xFFFFFFFF;
m_stencilMaskBack = 0xFFFFFFFF;
m_stencilFuncRef = 0;
m_stencilFuncRefBack = 0;
m_stencilFuncMask = 0xFFFFFFFF;
m_stencilFuncMaskBack = 0xFFFFFFFF;
m_numGLErrorsToConsoleAllowed = maxGLErrorsAllowedToConsole;
m_clearColor[0] = m_clearColor[1] = m_clearColor[2] = m_clearColor[3] = 0;
m_scissorEnabled = false;
m_clearDepth = 1;
m_clearStencil = 0;
m_colorMask[0] = m_colorMask[1] = m_colorMask[2] = m_colorMask[3] = true;
GLint numCombinedTextureImageUnits = 0;
webContext()->getIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &numCombinedTextureImageUnits);
m_textureUnits.clear();
m_textureUnits.resize(numCombinedTextureImageUnits);
GLint numVertexAttribs = 0;
webContext()->getIntegerv(GL_MAX_VERTEX_ATTRIBS, &numVertexAttribs);
m_maxVertexAttribs = numVertexAttribs;
m_maxTextureSize = 0;
webContext()->getIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
m_maxTextureLevel = WebGLTexture::computeLevelCount(m_maxTextureSize, m_maxTextureSize, 1);
m_maxCubeMapTextureSize = 0;
webContext()->getIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &m_maxCubeMapTextureSize);
m_maxCubeMapTextureLevel = WebGLTexture::computeLevelCount(m_maxCubeMapTextureSize, m_maxCubeMapTextureSize, 1);
m_maxRenderbufferSize = 0;
webContext()->getIntegerv(GL_MAX_RENDERBUFFER_SIZE, &m_maxRenderbufferSize);
// These two values from EXT_draw_buffers are lazily queried.
m_maxDrawBuffers = 0;
m_maxColorAttachments = 0;
m_backDrawBuffer = GL_BACK;
m_readBufferOfDefaultFramebuffer = GL_BACK;
if (isWebGL2OrHigher()) {
m_defaultVertexArrayObject = WebGLVertexArrayObject::create(this, WebGLVertexArrayObjectBase::VaoTypeDefault);
} else {
m_defaultVertexArrayObject = WebGLVertexArrayObjectOES::create(this, WebGLVertexArrayObjectBase::VaoTypeDefault);
}
addContextObject(m_defaultVertexArrayObject.get());
m_boundVertexArrayObject = m_defaultVertexArrayObject;
m_vertexAttribValue.resize(m_maxVertexAttribs);
createFallbackBlackTextures1x1();
webContext()->viewport(0, 0, drawingBufferWidth(), drawingBufferHeight());
webContext()->scissor(0, 0, drawingBufferWidth(), drawingBufferHeight());
m_contextLostCallbackAdapter = WebGLRenderingContextLostCallback::create(this);
m_errorMessageCallbackAdapter = WebGLRenderingContextErrorMessageCallback::create(this);
webContext()->setContextLostCallback(m_contextLostCallbackAdapter.get());
webContext()->setErrorMessageCallback(m_errorMessageCallbackAdapter.get());
// This ensures that the context has a valid "lastFlushID" and won't be mistakenly identified as the "least recently used" context.
webContext()->flush();
for (int i = 0; i < WebGLExtensionNameCount; ++i)
m_extensionEnabled[i] = false;
m_isWebGL2FormatsTypesAdded = false;
m_isOESTextureFloatFormatsTypesAdded = false;
m_isOESTextureHalfFloatFormatsTypesAdded = false;
m_isWebGLDepthTextureFormatsTypesAdded = false;
m_isEXTsRGBFormatsTypesAdded = false;
m_supportedInternalFormats.clear();
ADD_VALUES_TO_SET(m_supportedInternalFormats, kSupportedInternalFormatsES2);
m_supportedFormats.clear();
ADD_VALUES_TO_SET(m_supportedFormats, kSupportedFormatsES2);
m_supportedTypes.clear();
ADD_VALUES_TO_SET(m_supportedTypes, kSupportedTypesES2);
m_supportedFormatTypeCombinations.clear();
ADD_VALUES_TO_SET(m_supportedFormatTypeCombinations, kSupportedFormatTypesES2);
activateContext(this);
}
void WebGLRenderingContextBase::setupFlags()
{
ASSERT(drawingBuffer());
if (Page* p = canvas()->document().page()) {
m_synthesizedErrorsToConsole = p->settings().webGLErrorsToConsoleEnabled();
if (!m_multisamplingObserverRegistered && m_requestedAttributes.antialias()) {
m_multisamplingAllowed = drawingBuffer()->multisample();
p->addMultisamplingChangedObserver(this);
m_multisamplingObserverRegistered = true;
}
}
m_isDepthStencilSupported = extensionsUtil()->isExtensionEnabled("GL_OES_packed_depth_stencil");
}
void WebGLRenderingContextBase::addCompressedTextureFormat(GLenum format)
{
if (!m_compressedTextureFormats.contains(format))
m_compressedTextureFormats.append(format);
}
void WebGLRenderingContextBase::removeAllCompressedTextureFormats()
{
m_compressedTextureFormats.clear();
}
// Helper function for V8 bindings to identify what version of WebGL a CanvasRenderingContext supports.
unsigned WebGLRenderingContextBase::getWebGLVersion(const CanvasRenderingContext* context)
{
if (!context->is3d())
return 0;
return static_cast<const WebGLRenderingContextBase*>(context)->version();
}
WebGLRenderingContextBase::~WebGLRenderingContextBase()
{
// Remove all references to WebGLObjects so if they are the last reference
// they will be freed before the last context is removed from the context group.
m_boundArrayBuffer = nullptr;
m_defaultVertexArrayObject = nullptr;
m_boundVertexArrayObject = nullptr;
m_vertexAttrib0Buffer = nullptr;
m_currentProgram = nullptr;
m_framebufferBinding = nullptr;
m_renderbufferBinding = nullptr;
m_valuebufferBinding = nullptr;
// WebGLTexture shared objects will be detached and deleted
// m_contextGroup->removeContext(this), which will bring about deleteTexture() calls.
// We null these out to avoid accessing those members in deleteTexture().
for (size_t i = 0; i < m_textureUnits.size(); ++i) {
m_textureUnits[i].m_texture2DBinding = nullptr;
m_textureUnits[i].m_textureCubeMapBinding = nullptr;
m_textureUnits[i].m_texture3DBinding = nullptr;
m_textureUnits[i].m_texture2DArrayBinding = nullptr;
}
m_blackTexture2D = nullptr;
m_blackTextureCubeMap = nullptr;
detachAndRemoveAllObjects();
// Release all extensions now.
for (ExtensionTracker* tracker : m_extensions) {
tracker->loseExtension(true);
}
m_extensions.clear();
// Context must be removed from the group prior to the destruction of the
// WebGraphicsContext3D, otherwise shared objects may not be properly deleted.
m_contextGroup->removeContext(this);
destroyContext();
if (m_multisamplingObserverRegistered)
if (Page* page = canvas()->document().page())
page->removeMultisamplingChangedObserver(this);
willDestroyContext(this);
}
void WebGLRenderingContextBase::destroyContext()
{
if (!drawingBuffer())
return;
m_extensionsUtil.clear();
webContext()->setContextLostCallback(nullptr);
webContext()->setErrorMessageCallback(nullptr);
ASSERT(drawingBuffer());
m_drawingBuffer->beginDestruction();
m_drawingBuffer.clear();
}
void WebGLRenderingContextBase::markContextChanged(ContentChangeType changeType)
{
if (m_framebufferBinding || isContextLost())
return;
drawingBuffer()->markContentsChanged();
LayoutBox* layoutBox = canvas()->layoutBox();
if (layoutBox && layoutBox->hasAcceleratedCompositing()) {
m_markedCanvasDirty = true;
canvas()->clearCopiedImage();
layoutBox->contentChanged(changeType);
} else {
if (!m_markedCanvasDirty) {
m_markedCanvasDirty = true;
canvas()->didDraw(FloatRect(FloatPoint(0, 0), clampedCanvasSize()));
}
}
}
WebGLRenderingContextBase::HowToClear WebGLRenderingContextBase::clearIfComposited(GLbitfield mask)
{
if (isContextLost())
return Skipped;
if (!drawingBuffer()->bufferClearNeeded() || (mask && m_framebufferBinding))
return Skipped;
Nullable<WebGLContextAttributes> contextAttributes;
getContextAttributes(contextAttributes);
if (contextAttributes.isNull()) {
// Unlikely, but context was lost.
return Skipped;
}
// Determine if it's possible to combine the clear the user asked for and this clear.
bool combinedClear = mask && !m_scissorEnabled;
webContext()->disable(GL_SCISSOR_TEST);
if (combinedClear && (mask & GL_COLOR_BUFFER_BIT)) {
webContext()->clearColor(m_colorMask[0] ? m_clearColor[0] : 0,
m_colorMask[1] ? m_clearColor[1] : 0,
m_colorMask[2] ? m_clearColor[2] : 0,
m_colorMask[3] ? m_clearColor[3] : 0);
} else {
webContext()->clearColor(0, 0, 0, 0);
}
webContext()->colorMask(true, true, true, true);
GLbitfield clearMask = GL_COLOR_BUFFER_BIT;
if (contextAttributes.get().depth()) {
if (!combinedClear || !m_depthMask || !(mask & GL_DEPTH_BUFFER_BIT))
webContext()->clearDepth(1.0f);
clearMask |= GL_DEPTH_BUFFER_BIT;
webContext()->depthMask(true);
}
if (contextAttributes.get().stencil()) {
if (combinedClear && (mask & GL_STENCIL_BUFFER_BIT))
webContext()->clearStencil(m_clearStencil & m_stencilMask);
else
webContext()->clearStencil(0);
clearMask |= GL_STENCIL_BUFFER_BIT;
webContext()->stencilMaskSeparate(GL_FRONT, 0xFFFFFFFF);
}
drawingBuffer()->clearFramebuffers(clearMask);
restoreStateAfterClear();
drawingBuffer()->restoreFramebufferBindings();
drawingBuffer()->setBufferClearNeeded(false);
return combinedClear ? CombinedClear : JustClear;
}
void WebGLRenderingContextBase::restoreStateAfterClear()
{
if (isContextLost())
return;
// Restore the state that the context set.
if (m_scissorEnabled)
webContext()->enable(GL_SCISSOR_TEST);
webContext()->clearColor(m_clearColor[0], m_clearColor[1],
m_clearColor[2], m_clearColor[3]);
webContext()->colorMask(m_colorMask[0], m_colorMask[1],
m_colorMask[2], m_colorMask[3]);
webContext()->clearDepth(m_clearDepth);
webContext()->clearStencil(m_clearStencil);
webContext()->stencilMaskSeparate(GL_FRONT, m_stencilMask);
webContext()->depthMask(m_depthMask);
}
void WebGLRenderingContextBase::markLayerComposited()
{
if (!isContextLost())
drawingBuffer()->setBufferClearNeeded(true);
}
void WebGLRenderingContextBase::setIsHidden(bool hidden)
{
if (drawingBuffer())
drawingBuffer()->setIsHidden(hidden);
}
bool WebGLRenderingContextBase::paintRenderingResultsToCanvas(SourceDrawingBuffer sourceBuffer)
{
if (isContextLost())
return false;
bool mustClearNow = clearIfComposited() != Skipped;
if (!m_markedCanvasDirty && !mustClearNow)
return false;
canvas()->clearCopiedImage();
m_markedCanvasDirty = false;
ScopedTexture2DRestorer restorer(this);
ScopedFramebufferRestorer fboRestorer(this);
drawingBuffer()->commit();
if (!canvas()->buffer()->copyRenderingResultsFromDrawingBuffer(drawingBuffer(), sourceBuffer)) {
canvas()->ensureUnacceleratedImageBuffer();
if (canvas()->hasImageBuffer())
drawingBuffer()->paintRenderingResultsToCanvas(canvas()->buffer());
}
return true;
}
ImageData* WebGLRenderingContextBase::paintRenderingResultsToImageData(SourceDrawingBuffer sourceBuffer)
{
if (isContextLost())
return nullptr;
if (m_requestedAttributes.premultipliedAlpha())
return nullptr;
clearIfComposited();
drawingBuffer()->commit();
ScopedFramebufferRestorer restorer(this);
int width, height;
WTF::ArrayBufferContents contents;
if (!drawingBuffer()->paintRenderingResultsToImageData(width, height, sourceBuffer, contents))
return nullptr;
RefPtr<DOMArrayBuffer> imageDataPixels = DOMArrayBuffer::create(contents);
return ImageData::create(
IntSize(width, height),
DOMUint8ClampedArray::create(imageDataPixels, 0, imageDataPixels->byteLength()));
}
void WebGLRenderingContextBase::reshape(int width, int height)
{
if (isContextLost())
return;
// This is an approximation because at WebGLRenderingContextBase level we don't
// know if the underlying FBO uses textures or renderbuffers.
GLint maxSize = std::min(m_maxTextureSize, m_maxRenderbufferSize);
GLint maxWidth = std::min(maxSize, m_maxViewportDims[0]);
GLint maxHeight = std::min(maxSize, m_maxViewportDims[1]);
width = clamp(width, 1, maxWidth);
height = clamp(height, 1, maxHeight);
// Limit drawing buffer area to 4k*4k to avoid memory exhaustion. Width or height may be larger than
// 4k as long as it's within the max viewport dimensions and total area remains within the limit.
// For example: 5120x2880 should be fine.
const int maxArea = 4096 * 4096;
int currentArea = width * height;
if (currentArea > maxArea) {
// If we've exceeded the area limit scale the buffer down, preserving ascpect ratio, until it fits.
float scaleFactor = sqrtf(static_cast<float>(maxArea) / static_cast<float>(currentArea));
width = std::max(1, static_cast<int>(width * scaleFactor));
height = std::max(1, static_cast<int>(height * scaleFactor));
}
// We don't have to mark the canvas as dirty, since the newly created image buffer will also start off
// clear (and this matches what reshape will do).
drawingBuffer()->reset(IntSize(width, height));
restoreStateAfterClear();
webContext()->bindTexture(GL_TEXTURE_2D, objectOrZero(m_textureUnits[m_activeTextureUnit].m_texture2DBinding.get()));
webContext()->bindRenderbuffer(GL_RENDERBUFFER, objectOrZero(m_renderbufferBinding.get()));
drawingBuffer()->restoreFramebufferBindings();
}
int WebGLRenderingContextBase::drawingBufferWidth() const
{
return isContextLost() ? 0 : drawingBuffer()->size().width();
}
int WebGLRenderingContextBase::drawingBufferHeight() const
{
return isContextLost() ? 0 : drawingBuffer()->size().height();
}
unsigned WebGLRenderingContextBase::sizeInBytes(GLenum type)
{
switch (type) {
case GL_BYTE:
return sizeof(GLbyte);
case GL_UNSIGNED_BYTE:
return sizeof(GLubyte);
case GL_SHORT:
return sizeof(GLshort);
case GL_UNSIGNED_SHORT:
return sizeof(GLushort);
case GL_INT:
return sizeof(GLint);
case GL_UNSIGNED_INT:
return sizeof(GLuint);
case GL_FLOAT:
return sizeof(GLfloat);
}
ASSERT_NOT_REACHED();
return 0;
}
void WebGLRenderingContextBase::activeTexture(GLenum texture)
{
if (isContextLost())
return;
if (texture - GL_TEXTURE0 >= m_textureUnits.size()) {
synthesizeGLError(GL_INVALID_ENUM, "activeTexture", "texture unit out of range");
return;
}
m_activeTextureUnit = texture - GL_TEXTURE0;
webContext()->activeTexture(texture);
drawingBuffer()->setActiveTextureUnit(texture);
}
void WebGLRenderingContextBase::attachShader(WebGLProgram* program, WebGLShader* shader)
{
if (isContextLost() || !validateWebGLObject("attachShader", program) || !validateWebGLObject("attachShader", shader))
return;
if (!program->attachShader(shader)) {
synthesizeGLError(GL_INVALID_OPERATION, "attachShader", "shader attachment already has shader");
return;
}
webContext()->attachShader(objectOrZero(program), objectOrZero(shader));
shader->onAttached();
}
void WebGLRenderingContextBase::bindAttribLocation(WebGLProgram* program, GLuint index, const String& name)
{
if (isContextLost() || !validateWebGLObject("bindAttribLocation", program))
return;
if (!validateLocationLength("bindAttribLocation", name))
return;
if (!validateString("bindAttribLocation", name))
return;
if (isPrefixReserved(name)) {
synthesizeGLError(GL_INVALID_OPERATION, "bindAttribLocation", "reserved prefix");
return;
}
if (index >= m_maxVertexAttribs) {
synthesizeGLError(GL_INVALID_VALUE, "bindAttribLocation", "index out of range");
return;
}
webContext()->bindAttribLocation(objectOrZero(program), index, name.utf8().data());
}
bool WebGLRenderingContextBase::checkObjectToBeBound(const char* functionName, WebGLObject* object, bool& deleted)
{
deleted = false;
if (isContextLost())
return false;
if (object) {
if (!object->validate(contextGroup(), this)) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "object not from this context");
return false;
}
deleted = !object->hasObject();
}
return true;
}
bool WebGLRenderingContextBase::validateAndUpdateBufferBindTarget(const char* functionName, GLenum target, WebGLBuffer* buffer)
{
if (!validateBufferTarget(functionName, target))
return false;
if (buffer && buffer->getInitialTarget() && buffer->getInitialTarget() != target) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "buffers can not be used with multiple targets");
return false;
}
switch (target) {
case GL_ARRAY_BUFFER:
m_boundArrayBuffer = buffer;
break;
case GL_ELEMENT_ARRAY_BUFFER:
m_boundVertexArrayObject->setElementArrayBuffer(buffer);
break;
default:
ASSERT_NOT_REACHED();
return false;
}
if (buffer && !buffer->getInitialTarget())
buffer->setInitialTarget(target);
return true;
}
void WebGLRenderingContextBase::bindBuffer(GLenum target, WebGLBuffer* buffer)
{
bool deleted;
if (!checkObjectToBeBound("bindBuffer", buffer, deleted))
return;
if (deleted)
buffer = 0;
if (!validateAndUpdateBufferBindTarget("bindBuffer", target, buffer))
return;
webContext()->bindBuffer(target, objectOrZero(buffer));
}
void WebGLRenderingContextBase::bindFramebuffer(GLenum target, WebGLFramebuffer* buffer)
{
bool deleted;
if (!checkObjectToBeBound("bindFramebuffer", buffer, deleted))
return;
if (deleted)
buffer = 0;
if (target != GL_FRAMEBUFFER) {
synthesizeGLError(GL_INVALID_ENUM, "bindFramebuffer", "invalid target");
return;
}
setFramebuffer(target, buffer);
}
void WebGLRenderingContextBase::bindRenderbuffer(GLenum target, WebGLRenderbuffer* renderBuffer)
{
bool deleted;
if (!checkObjectToBeBound("bindRenderbuffer", renderBuffer, deleted))
return;
if (deleted)
renderBuffer = 0;
if (target != GL_RENDERBUFFER) {
synthesizeGLError(GL_INVALID_ENUM, "bindRenderbuffer", "invalid target");
return;
}
m_renderbufferBinding = renderBuffer;
webContext()->bindRenderbuffer(target, objectOrZero(renderBuffer));
if (renderBuffer)
renderBuffer->setHasEverBeenBound();
}
void WebGLRenderingContextBase::bindTexture(GLenum target, WebGLTexture* texture)
{
bool deleted;
if (!checkObjectToBeBound("bindTexture", texture, deleted))
return;
if (deleted)
texture = 0;
if (texture && texture->getTarget() && texture->getTarget() != target) {
synthesizeGLError(GL_INVALID_OPERATION, "bindTexture", "textures can not be used with multiple targets");
return;
}
if (target == GL_TEXTURE_2D) {
m_textureUnits[m_activeTextureUnit].m_texture2DBinding = texture;
if (!m_activeTextureUnit)
drawingBuffer()->setTexture2DBinding(objectOrZero(texture));
} else if (target == GL_TEXTURE_CUBE_MAP) {
m_textureUnits[m_activeTextureUnit].m_textureCubeMapBinding = texture;
} else if (isWebGL2OrHigher() && target == GL_TEXTURE_2D_ARRAY) {
m_textureUnits[m_activeTextureUnit].m_texture2DArrayBinding = texture;
} else if (isWebGL2OrHigher() && target == GL_TEXTURE_3D) {
m_textureUnits[m_activeTextureUnit].m_texture3DBinding = texture;
} else {
synthesizeGLError(GL_INVALID_ENUM, "bindTexture", "invalid target");
return;
}
webContext()->bindTexture(target, objectOrZero(texture));
if (texture) {
texture->setTarget(target, getMaxTextureLevelForTarget(target));
m_onePlusMaxNonDefaultTextureUnit = max(m_activeTextureUnit + 1, m_onePlusMaxNonDefaultTextureUnit);
} else {
// If the disabled index is the current maximum, trace backwards to find the new max enabled texture index
if (m_onePlusMaxNonDefaultTextureUnit == m_activeTextureUnit + 1) {
findNewMaxNonDefaultTextureUnit();
}
}
// Note: previously we used to automatically set the TEXTURE_WRAP_R
// repeat mode to CLAMP_TO_EDGE for cube map textures, because OpenGL
// ES 2.0 doesn't expose this flag (a bug in the specification) and
// otherwise the application has no control over the seams in this
// dimension. However, it appears that supporting this properly on all
// platforms is fairly involved (will require a HashMap from texture ID
// in all ports), and we have not had any complaints, so the logic has
// been removed.
}
void WebGLRenderingContextBase::blendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
{
if (isContextLost())
return;
webContext()->blendColor(red, green, blue, alpha);
}
void WebGLRenderingContextBase::blendEquation(GLenum mode)
{
if (isContextLost() || !validateBlendEquation("blendEquation", mode))
return;
webContext()->blendEquation(mode);
}
void WebGLRenderingContextBase::blendEquationSeparate(GLenum modeRGB, GLenum modeAlpha)
{
if (isContextLost() || !validateBlendEquation("blendEquationSeparate", modeRGB) || !validateBlendEquation("blendEquationSeparate", modeAlpha))
return;
webContext()->blendEquationSeparate(modeRGB, modeAlpha);
}
void WebGLRenderingContextBase::blendFunc(GLenum sfactor, GLenum dfactor)
{
if (isContextLost() || !validateBlendFuncFactors("blendFunc", sfactor, dfactor))
return;
webContext()->blendFunc(sfactor, dfactor);
}
void WebGLRenderingContextBase::blendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha)
{
// Note: Alpha does not have the same restrictions as RGB.
if (isContextLost() || !validateBlendFuncFactors("blendFuncSeparate", srcRGB, dstRGB))
return;
webContext()->blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
}
void WebGLRenderingContextBase::bufferDataImpl(GLenum target, long long size, const void* data, GLenum usage)
{
WebGLBuffer* buffer = validateBufferDataTarget("bufferData", target);
if (!buffer)
return;
if (!validateBufferDataUsage("bufferData", usage))
return;
if (!validateValueFitNonNegInt32("bufferData", "size", size))
return;
buffer->setSize(size);
webContext()->bufferData(target, static_cast<GLsizeiptr>(size), data, usage);
}
void WebGLRenderingContextBase::bufferData(GLenum target, long long size, GLenum usage)
{
if (isContextLost())
return;
bufferDataImpl(target, size, 0, usage);
}
void WebGLRenderingContextBase::bufferData(GLenum target, DOMArrayBuffer* data, GLenum usage)
{
if (isContextLost())
return;
if (!data) {
synthesizeGLError(GL_INVALID_VALUE, "bufferData", "no data");
return;
}
bufferDataImpl(target, data->byteLength(), data->data(), usage);
}
void WebGLRenderingContextBase::bufferData(GLenum target, DOMArrayBufferView* data, GLenum usage)
{
if (isContextLost())
return;
if (!data) {
synthesizeGLError(GL_INVALID_VALUE, "bufferData", "no data");
return;
}
bufferDataImpl(target, data->byteLength(), data->baseAddress(), usage);
}
void WebGLRenderingContextBase::bufferSubDataImpl(GLenum target, long long offset, GLsizeiptr size, const void* data)
{
WebGLBuffer* buffer = validateBufferDataTarget("bufferSubData", target);
if (!buffer)
return;
if (!validateValueFitNonNegInt32("bufferSubData", "offset", offset))
return;
if (!data)
return;
if (offset + static_cast<long long>(size) > buffer->getSize()) {
synthesizeGLError(GL_INVALID_VALUE, "bufferSubData", "buffer overflow");
return;
}
webContext()->bufferSubData(target, static_cast<GLintptr>(offset), size, data);
}
void WebGLRenderingContextBase::bufferSubData(GLenum target, long long offset, DOMArrayBuffer* data)
{
if (isContextLost())
return;
if (!data)
return;
bufferSubDataImpl(target, offset, data->byteLength(), data->data());
}
void WebGLRenderingContextBase::bufferSubData(GLenum target, long long offset, const FlexibleArrayBufferView& data)
{
if (isContextLost())
return;
if (!data)
return;
bufferSubDataImpl(target, offset, data.byteLength(), data.baseAddressMaybeOnStack());
}
bool WebGLRenderingContextBase::validateFramebufferTarget(GLenum target)
{
if (target == GL_FRAMEBUFFER)
return true;
return false;
}
WebGLFramebuffer* WebGLRenderingContextBase::getFramebufferBinding(GLenum target)
{
if (target == GL_FRAMEBUFFER)
return m_framebufferBinding.get();
return nullptr;
}
GLenum WebGLRenderingContextBase::checkFramebufferStatus(GLenum target)
{
if (isContextLost())
return GL_FRAMEBUFFER_UNSUPPORTED;
if (!validateFramebufferTarget(target)) {
synthesizeGLError(GL_INVALID_ENUM, "checkFramebufferStatus", "invalid target");
return 0;
}
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
if (!framebufferBinding || !framebufferBinding->object())
return GL_FRAMEBUFFER_COMPLETE;
const char* reason = "framebuffer incomplete";
GLenum result = framebufferBinding->checkStatus(&reason);
if (result != GL_FRAMEBUFFER_COMPLETE) {
emitGLWarning("checkFramebufferStatus", reason);
return result;
}
result = webContext()->checkFramebufferStatus(target);
return result;
}
void WebGLRenderingContextBase::clear(GLbitfield mask)
{
if (isContextLost())
return;
if (mask & ~(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) {
synthesizeGLError(GL_INVALID_VALUE, "clear", "invalid mask");
return;
}
const char* reason = "framebuffer incomplete";
if (m_framebufferBinding && !m_framebufferBinding->onAccess(webContext(), &reason)) {
synthesizeGLError(GL_INVALID_FRAMEBUFFER_OPERATION, "clear", reason);
return;
}
if (clearIfComposited(mask) != CombinedClear)
webContext()->clear(mask);
markContextChanged(CanvasChanged);
}
void WebGLRenderingContextBase::clearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
if (isContextLost())
return;
if (std::isnan(r))
r = 0;
if (std::isnan(g))
g = 0;
if (std::isnan(b))
b = 0;
if (std::isnan(a))
a = 1;
m_clearColor[0] = r;
m_clearColor[1] = g;
m_clearColor[2] = b;
m_clearColor[3] = a;
webContext()->clearColor(r, g, b, a);
}
void WebGLRenderingContextBase::clearDepth(GLfloat depth)
{
if (isContextLost())
return;
m_clearDepth = depth;
webContext()->clearDepth(depth);
}
void WebGLRenderingContextBase::clearStencil(GLint s)
{
if (isContextLost())
return;
m_clearStencil = s;
webContext()->clearStencil(s);
}
void WebGLRenderingContextBase::colorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)
{
if (isContextLost())
return;
m_colorMask[0] = red;
m_colorMask[1] = green;
m_colorMask[2] = blue;
m_colorMask[3] = alpha;
webContext()->colorMask(red, green, blue, alpha);
}
void WebGLRenderingContextBase::compileShader(WebGLShader* shader)
{
if (isContextLost() || !validateWebGLObject("compileShader", shader))
return;
webContext()->compileShader(objectOrZero(shader));
}
void WebGLRenderingContextBase::compressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, DOMArrayBufferView* data)
{
if (isContextLost())
return;
if (!validateTexFuncLevel("compressedTexImage2D", target, level))
return;
if (!validateCompressedTexFormat(internalformat)) {
synthesizeGLError(GL_INVALID_ENUM, "compressedTexImage2D", "invalid internalformat");
return;
}
if (border) {
synthesizeGLError(GL_INVALID_VALUE, "compressedTexImage2D", "border not 0");
return;
}
if (!validateCompressedTexDimensions("compressedTexImage2D", NotTexSubImage2D, target, level, width, height, internalformat))
return;
if (!validateCompressedTexFuncData("compressedTexImage2D", width, height, internalformat, data))
return;
WebGLTexture* tex = validateTextureBinding("compressedTexImage2D", target, true);
if (!tex)
return;
if (tex->isImmutable()) {
synthesizeGLError(GL_INVALID_OPERATION, "compressedTexImage2D", "attempted to modify immutable texture");
return;
}
if (isNPOTStrict() && level && WebGLTexture::isNPOT(width, height)) {
synthesizeGLError(GL_INVALID_VALUE, "compressedTexImage2D", "level > 0 not power of 2");
return;
}
webContext()->compressedTexImage2D(target, level, internalformat, width, height,
border, data->byteLength(), data->baseAddress());
tex->setLevelInfo(target, level, internalformat, width, height, 1, GL_UNSIGNED_BYTE);
}
void WebGLRenderingContextBase::compressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, DOMArrayBufferView* data)
{
if (isContextLost())
return;
if (!validateTexFuncLevel("compressedTexSubImage2D", target, level))
return;
if (!validateCompressedTexFormat(format)) {
synthesizeGLError(GL_INVALID_ENUM, "compressedTexSubImage2D", "invalid format");
return;
}
if (!validateCompressedTexFuncData("compressedTexSubImage2D", width, height, format, data))
return;
WebGLTexture* tex = validateTextureBinding("compressedTexSubImage2D", target, true);
if (!tex)
return;
if (!isWebGL2OrHigher() && format != tex->getInternalFormat(target, level)) {
synthesizeGLError(GL_INVALID_OPERATION, "compressedTexSubImage2D", "format does not match texture format");
return;
}
if (!validateCompressedTexSubDimensions("compressedTexSubImage2D", target, level, xoffset, yoffset, width, height, format, tex))
return;
webContext()->compressedTexSubImage2D(target, level, xoffset, yoffset,
width, height, format, data->byteLength(), data->baseAddress());
}
bool WebGLRenderingContextBase::validateSettableTexFormat(const char* functionName, GLenum format)
{
if (isWebGL2OrHigher())
return true;
if (WebGLImageConversion::getChannelBitsByFormat(format) & WebGLImageConversion::ChannelDepthStencil) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "format can not be set, only rendered to");
return false;
}
return true;
}
void WebGLRenderingContextBase::copyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)
{
if (isContextLost())
return;
if (!validateTexFuncLevel("copyTexImage2D", target, level))
return;
if (!validateTexFuncParameters("copyTexImage2D", NotTexSubImage2D, target, level, internalformat, width, height, border, internalformat, GL_UNSIGNED_BYTE))
return;
if (!validateSettableTexFormat("copyTexImage2D", internalformat))
return;
WebGLTexture* tex = validateTextureBinding("copyTexImage2D", target, true);
if (!tex)
return;
if (tex->isImmutable()) {
synthesizeGLError(GL_INVALID_OPERATION, "copyTexImage2D", "attempted to modify immutable texture");
return;
}
if (!isTexInternalFormatColorBufferCombinationValid(internalformat, boundFramebufferColorFormat())) {
synthesizeGLError(GL_INVALID_OPERATION, "copyTexImage2D", "framebuffer is incompatible format");
return;
}
if (isNPOTStrict() && level && WebGLTexture::isNPOT(width, height)) {
synthesizeGLError(GL_INVALID_VALUE, "copyTexImage2D", "level > 0 not power of 2");
return;
}
WebGLFramebuffer* readFramebufferBinding = nullptr;
if (!validateReadBufferAndGetInfo("copyTexImage2D", readFramebufferBinding, nullptr, nullptr))
return;
clearIfComposited();
ScopedDrawingBufferBinder binder(drawingBuffer(), readFramebufferBinding);
webContext()->copyTexImage2D(target, level, internalformat, x, y, width, height, border);
// FIXME: if the framebuffer is not complete, none of the below should be executed.
tex->setLevelInfo(target, level, internalformat, width, height, 1, GL_UNSIGNED_BYTE);
}
void WebGLRenderingContextBase::copyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
{
if (isContextLost())
return;
if (!validateCopyTexSubImage("copyTexSubImage2D", target, level, xoffset, yoffset, 0, x, y, width, height))
return;
WebGLFramebuffer* readFramebufferBinding = nullptr;
if (!validateReadBufferAndGetInfo("copyTexSubImage2D", readFramebufferBinding, nullptr, nullptr))
return;
clearIfComposited();
ScopedDrawingBufferBinder binder(drawingBuffer(), readFramebufferBinding);
webContext()->copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
WebGLBuffer* WebGLRenderingContextBase::createBuffer()
{
if (isContextLost())
return nullptr;
WebGLBuffer* o = WebGLBuffer::create(this);
addSharedObject(o);
return o;
}
WebGLFramebuffer* WebGLRenderingContextBase::createFramebuffer()
{
if (isContextLost())
return nullptr;
WebGLFramebuffer* o = WebGLFramebuffer::create(this);
addContextObject(o);
return o;
}
WebGLTexture* WebGLRenderingContextBase::createTexture()
{
if (isContextLost())
return nullptr;
WebGLTexture* o = WebGLTexture::create(this);
addSharedObject(o);
return o;
}
WebGLProgram* WebGLRenderingContextBase::createProgram()
{
if (isContextLost())
return nullptr;
WebGLProgram* o = WebGLProgram::create(this);
addSharedObject(o);
return o;
}
WebGLRenderbuffer* WebGLRenderingContextBase::createRenderbuffer()
{
if (isContextLost())
return nullptr;
WebGLRenderbuffer* o = WebGLRenderbuffer::create(this);
addSharedObject(o);
return o;
}
WebGLRenderbuffer* WebGLRenderingContextBase::ensureEmulatedStencilBuffer(GLenum target, WebGLRenderbuffer* renderbuffer)
{
if (isContextLost())
return nullptr;
if (!renderbuffer->emulatedStencilBuffer()) {
renderbuffer->setEmulatedStencilBuffer(createRenderbuffer());
webContext()->bindRenderbuffer(target, objectOrZero(renderbuffer->emulatedStencilBuffer()));
webContext()->bindRenderbuffer(target, objectOrZero(m_renderbufferBinding.get()));
}
return renderbuffer->emulatedStencilBuffer();
}
WebGLShader* WebGLRenderingContextBase::createShader(GLenum type)
{
if (isContextLost())
return nullptr;
if (type != GL_VERTEX_SHADER && type != GL_FRAGMENT_SHADER) {
synthesizeGLError(GL_INVALID_ENUM, "createShader", "invalid shader type");
return nullptr;
}
WebGLShader* o = WebGLShader::create(this, type);
addSharedObject(o);
return o;
}
void WebGLRenderingContextBase::cullFace(GLenum mode)
{
if (isContextLost())
return;
switch (mode) {
case GL_FRONT_AND_BACK:
case GL_FRONT:
case GL_BACK:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "cullFace", "invalid mode");
return;
}
webContext()->cullFace(mode);
}
bool WebGLRenderingContextBase::deleteObject(WebGLObject* object)
{
if (isContextLost() || !object)
return false;
if (!object->validate(contextGroup(), this)) {
synthesizeGLError(GL_INVALID_OPERATION, "delete", "object does not belong to this context");
return false;
}
if (object->hasObject()) {
// We need to pass in context here because we want
// things in this context unbound.
object->deleteObject(webContext());
}
return true;
}
void WebGLRenderingContextBase::deleteBuffer(WebGLBuffer* buffer)
{
if (!deleteObject(buffer))
return;
removeBoundBuffer(buffer);
}
void WebGLRenderingContextBase::deleteFramebuffer(WebGLFramebuffer* framebuffer)
{
if (!deleteObject(framebuffer))
return;
if (framebuffer == m_framebufferBinding) {
m_framebufferBinding = nullptr;
drawingBuffer()->setFramebufferBinding(GL_FRAMEBUFFER, 0);
// Have to call drawingBuffer()->bind() here to bind back to internal fbo.
drawingBuffer()->bind(GL_FRAMEBUFFER);
}
}
void WebGLRenderingContextBase::deleteProgram(WebGLProgram* program)
{
deleteObject(program);
// We don't reset m_currentProgram to 0 here because the deletion of the
// current program is delayed.
}
void WebGLRenderingContextBase::deleteRenderbuffer(WebGLRenderbuffer* renderbuffer)
{
if (!deleteObject(renderbuffer))
return;
if (renderbuffer == m_renderbufferBinding)
m_renderbufferBinding = nullptr;
if (m_framebufferBinding)
m_framebufferBinding->removeAttachmentFromBoundFramebuffer(GL_FRAMEBUFFER, renderbuffer);
if (getFramebufferBinding(GL_READ_FRAMEBUFFER))
getFramebufferBinding(GL_READ_FRAMEBUFFER)->removeAttachmentFromBoundFramebuffer(GL_READ_FRAMEBUFFER, renderbuffer);
}
void WebGLRenderingContextBase::deleteShader(WebGLShader* shader)
{
deleteObject(shader);
}
void WebGLRenderingContextBase::deleteTexture(WebGLTexture* texture)
{
if (!deleteObject(texture))
return;
int maxBoundTextureIndex = -1;
for (size_t i = 0; i < m_onePlusMaxNonDefaultTextureUnit; ++i) {
if (texture == m_textureUnits[i].m_texture2DBinding) {
m_textureUnits[i].m_texture2DBinding = nullptr;
maxBoundTextureIndex = i;
if (!i)
drawingBuffer()->setTexture2DBinding(0);
}
if (texture == m_textureUnits[i].m_textureCubeMapBinding) {
m_textureUnits[i].m_textureCubeMapBinding = nullptr;
maxBoundTextureIndex = i;
}
if (isWebGL2OrHigher()) {
if (texture == m_textureUnits[i].m_texture3DBinding) {
m_textureUnits[i].m_texture3DBinding = nullptr;
maxBoundTextureIndex = i;
}
if (texture == m_textureUnits[i].m_texture2DArrayBinding) {
m_textureUnits[i].m_texture2DArrayBinding = nullptr;
maxBoundTextureIndex = i;
}
}
}
if (m_framebufferBinding)
m_framebufferBinding->removeAttachmentFromBoundFramebuffer(GL_FRAMEBUFFER, texture);
if (getFramebufferBinding(GL_READ_FRAMEBUFFER))
getFramebufferBinding(GL_READ_FRAMEBUFFER)->removeAttachmentFromBoundFramebuffer(GL_READ_FRAMEBUFFER, texture);
// If the deleted was bound to the the current maximum index, trace backwards to find the new max texture index
if (m_onePlusMaxNonDefaultTextureUnit == static_cast<unsigned long>(maxBoundTextureIndex + 1)) {
findNewMaxNonDefaultTextureUnit();
}
}
void WebGLRenderingContextBase::depthFunc(GLenum func)
{
if (isContextLost())
return;
if (!validateStencilOrDepthFunc("depthFunc", func))
return;
webContext()->depthFunc(func);
}
void WebGLRenderingContextBase::depthMask(GLboolean flag)
{
if (isContextLost())
return;
m_depthMask = flag;
webContext()->depthMask(flag);
}
void WebGLRenderingContextBase::depthRange(GLfloat zNear, GLfloat zFar)
{
if (isContextLost())
return;
if (zNear > zFar) {
synthesizeGLError(GL_INVALID_OPERATION, "depthRange", "zNear > zFar");
return;
}
webContext()->depthRange(zNear, zFar);
}
void WebGLRenderingContextBase::detachShader(WebGLProgram* program, WebGLShader* shader)
{
if (isContextLost() || !validateWebGLObject("detachShader", program) || !validateWebGLObject("detachShader", shader))
return;
if (!program->detachShader(shader)) {
synthesizeGLError(GL_INVALID_OPERATION, "detachShader", "shader not attached");
return;
}
webContext()->detachShader(objectOrZero(program), objectOrZero(shader));
shader->onDetached(webContext());
}
void WebGLRenderingContextBase::disable(GLenum cap)
{
if (isContextLost() || !validateCapability("disable", cap))
return;
if (cap == GL_STENCIL_TEST) {
m_stencilEnabled = false;
applyStencilTest();
return;
}
if (cap == GL_SCISSOR_TEST) {
m_scissorEnabled = false;
drawingBuffer()->setScissorEnabled(m_scissorEnabled);
}
webContext()->disable(cap);
}
void WebGLRenderingContextBase::disableVertexAttribArray(GLuint index)
{
if (isContextLost())
return;
if (index >= m_maxVertexAttribs) {
synthesizeGLError(GL_INVALID_VALUE, "disableVertexAttribArray", "index out of range");
return;
}
WebGLVertexArrayObjectBase::VertexAttribState* state = m_boundVertexArrayObject->getVertexAttribState(index);
state->enabled = false;
webContext()->disableVertexAttribArray(index);
}
bool WebGLRenderingContextBase::validateRenderingState(const char* functionName)
{
if (!m_currentProgram) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "no valid shader program in use");
return false;
}
return true;
}
bool WebGLRenderingContextBase::validateWebGLObject(const char* functionName, WebGLObject* object)
{
if (!object || !object->hasObject()) {
synthesizeGLError(GL_INVALID_VALUE, functionName, "no object or object deleted");
return false;
}
if (!object->validate(contextGroup(), this)) {
synthesizeGLError(GL_INVALID_OPERATION, functionName, "object does not belong to this context");
return false;
}
return true;
}
void WebGLRenderingContextBase::drawArrays(GLenum mode, GLint first, GLsizei count)
{
if (!validateDrawArrays("drawArrays", mode, first, count))
return;
clearIfComposited();
handleTextureCompleteness("drawArrays", true);
webContext()->drawArrays(mode, first, count);
handleTextureCompleteness("drawArrays", false);
markContextChanged(CanvasChanged);
}
void WebGLRenderingContextBase::drawElements(GLenum mode, GLsizei count, GLenum type, long long offset)
{
if (!validateDrawElements("drawElements", mode, count, type, offset))
return;
clearIfComposited();
handleTextureCompleteness("drawElements", true);
webContext()->drawElements(mode, count, type, static_cast<GLintptr>(offset));
handleTextureCompleteness("drawElements", false);
markContextChanged(CanvasChanged);
}
void WebGLRenderingContextBase::drawArraysInstancedANGLE(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
{
if (!validateDrawArrays("drawArraysInstancedANGLE", mode, first, count))
return;
if (!validateDrawInstanced("drawArraysInstancedANGLE", primcount))
return;
clearIfComposited();
handleTextureCompleteness("drawArraysInstancedANGLE", true);
webContext()->drawArraysInstancedANGLE(mode, first, count, primcount);
handleTextureCompleteness("drawArraysInstancedANGLE", false);
markContextChanged(CanvasChanged);
}
void WebGLRenderingContextBase::drawElementsInstancedANGLE(GLenum mode, GLsizei count, GLenum type, long long offset, GLsizei primcount)
{
if (!validateDrawElements("drawElementsInstancedANGLE", mode, count, type, offset))
return;
if (!validateDrawInstanced("drawElementsInstancedANGLE", primcount))
return;
clearIfComposited();
handleTextureCompleteness("drawElementsInstancedANGLE", true);
webContext()->drawElementsInstancedANGLE(mode, count, type, static_cast<GLintptr>(offset), primcount);
handleTextureCompleteness("drawElementsInstancedANGLE", false);
markContextChanged(CanvasChanged);
}
void WebGLRenderingContextBase::enable(GLenum cap)
{
if (isContextLost() || !validateCapability("enable", cap))
return;
if (cap == GL_STENCIL_TEST) {
m_stencilEnabled = true;
applyStencilTest();
return;
}
if (cap == GL_SCISSOR_TEST) {
m_scissorEnabled = true;
drawingBuffer()->setScissorEnabled(m_scissorEnabled);
}
webContext()->enable(cap);
}
void WebGLRenderingContextBase::enableVertexAttribArray(GLuint index)
{
if (isContextLost())
return;
if (index >= m_maxVertexAttribs) {
synthesizeGLError(GL_INVALID_VALUE, "enableVertexAttribArray", "index out of range");
return;
}
WebGLVertexArrayObjectBase::VertexAttribState* state = m_boundVertexArrayObject->getVertexAttribState(index);
state->enabled = true;
webContext()->enableVertexAttribArray(index);
}
void WebGLRenderingContextBase::finish()
{
if (isContextLost())
return;
webContext()->flush(); // Intentionally a flush, not a finish.
}
void WebGLRenderingContextBase::flush()
{
if (isContextLost())
return;
webContext()->flush();
}
void WebGLRenderingContextBase::framebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, WebGLRenderbuffer* buffer)
{
if (isContextLost() || !validateFramebufferFuncParameters("framebufferRenderbuffer", target, attachment))
return;
if (renderbuffertarget != GL_RENDERBUFFER) {
synthesizeGLError(GL_INVALID_ENUM, "framebufferRenderbuffer", "invalid target");
return;
}
if (buffer && !buffer->validate(contextGroup(), this)) {
synthesizeGLError(GL_INVALID_OPERATION, "framebufferRenderbuffer", "no buffer or buffer not from this context");
return;
}
// Don't allow the default framebuffer to be mutated; all current
// implementations use an FBO internally in place of the default
// FBO.
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
if (!framebufferBinding || !framebufferBinding->object()) {
synthesizeGLError(GL_INVALID_OPERATION, "framebufferRenderbuffer", "no framebuffer bound");
return;
}
Platform3DObject bufferObject = objectOrZero(buffer);
switch (attachment) {
case GL_DEPTH_STENCIL_ATTACHMENT:
if (isWebGL2OrHigher() || isDepthStencilSupported() || !buffer) {
webContext()->framebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, renderbuffertarget, bufferObject);
webContext()->framebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, renderbuffertarget, bufferObject);
} else {
WebGLRenderbuffer* emulatedStencilBuffer = ensureEmulatedStencilBuffer(renderbuffertarget, buffer);
if (!emulatedStencilBuffer) {
synthesizeGLError(GL_OUT_OF_MEMORY, "framebufferRenderbuffer", "out of memory");
return;
}
webContext()->framebufferRenderbuffer(target, GL_DEPTH_ATTACHMENT, renderbuffertarget, bufferObject);
webContext()->framebufferRenderbuffer(target, GL_STENCIL_ATTACHMENT, renderbuffertarget, objectOrZero(emulatedStencilBuffer));
}
break;
default:
webContext()->framebufferRenderbuffer(target, attachment, renderbuffertarget, bufferObject);
}
if (isWebGL2OrHigher() && 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, buffer);
framebufferBinding->setAttachmentForBoundFramebuffer(target, GL_STENCIL_ATTACHMENT, buffer);
} else {
framebufferBinding->setAttachmentForBoundFramebuffer(target, attachment, buffer);
}
applyStencilTest();
}
void WebGLRenderingContextBase::framebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, WebGLTexture* texture, GLint level)
{
if (isContextLost() || !validateFramebufferFuncParameters("framebufferTexture2D", target, attachment))
return;
if (isWebGL2OrHigher()) {
if (!validateTexFuncLevel("framebufferTexture2D", textarget, level))
return;
} else if (level) {
synthesizeGLError(GL_INVALID_VALUE, "framebufferTexture2D", "level not 0");
return;
}
if (texture && !texture->validate(contextGroup(), this)) {
synthesizeGLError(GL_INVALID_OPERATION, "framebufferTexture2D", "no texture or texture not from this context");
return;
}
// Don't allow the default framebuffer to be mutated; all current
// implementations use an FBO internally in place of the default
// FBO.
WebGLFramebuffer* framebufferBinding = getFramebufferBinding(target);
if (!framebufferBinding || !framebufferBinding->object()) {
synthesizeGLError(GL_INVALID_OPERATION, "framebufferTexture2D", "no framebuffer bound");
return;
}
Platform3DObject textureObject = objectOrZero(texture);
switch (attachment) {
case GL_DEPTH_STENCIL_ATTACHMENT:
webContext()->framebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, textureObject, level);
webContext()->framebufferTexture2D(target, GL_STENCIL_ATTACHMENT, textarget, textureObject, level);
break;
case GL_DEPTH_ATTACHMENT:
webContext()->framebufferTexture2D(target, attachment, textarget, textureObject, level);
break;
case GL_STENCIL_ATTACHMENT:
webContext()->framebufferTexture2D(target, attachment, textarget, textureObject, level);
break;
default:
webContext()->framebufferTexture2D(target, attachment, textarget, textureObject, level);
}
framebufferBinding->setAttachmentForBoundFramebuffer(target, attachment, textarget, texture, level);
applyStencilTest();
}
void WebGLRenderingContextBase::frontFace(GLenum mode)
{
if (isContextLost())
return;
switch (mode) {
case GL_CW:
case GL_CCW:
break;
default:
synthesizeGLError(GL_INVALID_ENUM, "frontFace", "invalid mode");
return;
}
webContext()->frontFace(mode);
}
void WebGLRenderingContextBase::generateMipmap(GLenum target)
{
if (isContextLost())
return;
WebGLTexture* tex = validateTextureBinding("generateMipmap", target, false);
if (!tex)
return;
if (!tex->canGenerateMipmaps()) {
synthesizeGLError(GL_INVALID_OPERATION, "generateMipmap", "cannot generate mipmaps");
return;
}
if (tex->getInternalFormat(target, 0) == GL_SRGB_EXT || tex->getInternalFormat(target, 0) == GL_SRGB_ALPHA_EXT) {
synthesizeGLError(GL_INVALID_OPERATION, "generateMipmap", "cannot generate mipmaps for sRGB textures");
return;
}
if (!validateSettableTexFormat("generateMipmap", tex->getInternalFormat(target, 0)))
return;
// generateMipmap won't work properly if minFilter is not NEAREST_MIPMAP_LINEAR
// on Mac. Remove the hack once this driver bug is fixed.
#if OS(MACOSX)
bool needToResetMinFilter = false;
if (tex->getMinFilter() != GL_NEAREST_MIPMAP_LINEAR) {
webContext()->texParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
needToResetMinFilter = true;
}
#endif
webContext()->generateMipmap(target);
#if OS(MACOSX)
if (needToResetMinFilter)
webContext()->texParameteri(target, GL_TEXTURE_MIN_FILTER, tex->getMinFilter());
#endif
tex->generateMipmapLevelInfo();
}
WebGLActiveInfo* WebGLRenderingContextBase::getActiveAttrib(WebGLProgram* program, GLuint index)
{
if (isContextLost() || !validateWebGLObject("getActiveAttrib", program))
return nullptr;
WebGraphicsContext3D::ActiveInfo info;
if (!webContext()->getActiveAttrib(objectOrZero(program), index, info))
return nullptr;
return WebGLActiveInfo::create(info.name, info.type, info.size);
}
WebGLActiveInfo* WebGLRenderingContextBase::getActiveUniform(WebGLProgram* program, GLuint index)
{
if (isContextLost() || !validateWebGLObject("getActiveUniform", program))
return nullptr;
WebGraphicsContext3D::ActiveInfo info;
if (!webContext()->getActiveUniform(objectOrZero(program), index, info))
return nullptr;
return WebGLActiveInfo::create(info.name, info.type, info.size);
}
Nullable<HeapVector<Member<WebGLShader>>> WebGLRenderingContextBase::getAttachedShaders(WebGLProgram* program)
{
if (isContextLost() || !validateWebGLObject("getAttachedShaders", program))
return nullptr;
HeapVector<Member<WebGLShader>> shaderObjects;
const GLenum shaderType[] = {
GL_VERTEX_SHADER,
GL_FRAGMENT_SHADER
};
for (unsigned i = 0; i < sizeof(shaderType) / sizeof(GLenum); ++i) {
WebGLShader* shader = program->getAttachedShader(shaderType[i]);
if (shader)
shaderObjects.append(shader);
}
return shaderObjects;
}
GLint WebGLRenderingContextBase::getAttribLocation(WebGLProgram* program, const String& name)
{
if (isContextLost() || !validateWebGLObject("getAttribLocation", program))
return -1;
if (!validateLocationLength("getAttribLocation", name))
return -1;
if (!validateString("getAttribLocation", name))
return -1;
if (isPrefixReserved(name))
return -1;
if (!program->linkStatus()) {
synthesizeGLError(GL_INVALID_OPERATION, "getAttribLocation", "program not linked");
return 0;
}
return webContext()->getAttribLocation(objectOrZero(program), name.utf8().data());
}
bool WebGLRenderingContextBase::validateBufferTarget(const char* functionName, GLenum target)
{
switch (target) {
case GL_ARRAY_BUFFER:
case GL_ELEMENT_ARRAY_BUFFER:
return true;
default:
synthesizeGLError(GL_INVALID_ENUM, functionName, "invalid target");
return false;
}
}
ScriptValue WebGLRenderingContextBase::getBufferParameter(ScriptState* scriptState, GLenum target, GLenum pname)
{
if (isContextLost() || !validateBufferTarget("getBufferParameter", target))
return ScriptValue::createNull(scriptState);
switch (pname) {
case GL_BUFFER_USAGE:
{
GLint value = 0;
webContext()->getBufferParameteriv(target, pname, &value);
return WebGLAny(scriptState, static_cast<unsigned>(value));
}
case GL_BUFFER_SIZE:
{
GLint value = 0;
webContext()->getBufferParameteriv(target, pname, &value);
if (!isWebGL2OrHigher())
return WebGLAny(scriptState, value);
return WebGLAny(scriptState, static_cast<GLint64>(value));
}
default:
synthesizeGLError(GL_INVALID_ENUM, "getBufferParameter", "invalid parameter name");
return ScriptValue::createNull(scriptState);
}
}
void WebGLRenderingContextBase::getContextAttributes(Nullable<WebGLContextAttributes>& result)
{
if (isContextLost())
return;
result.set(m_requestedAttributes);
// Some requested attributes may not be honored, so we need to query the underlying
// context/drawing buffer and adjust accordingly.
WebGraphicsContext3D::Attributes attrs = drawingBuffer()->getActualAttributes();
if (m_requestedAttributes.depth() && !attrs.depth)
result.get().setDepth(false);
if (m_requestedAttributes.stencil() && !attrs.stencil)
result.get().setStencil(false);
result.get().setAntialias(drawingBuffer()->multisample());
}
GLenum WebGLRenderingContextBase::getError()
{
if (m_lostContextErrors.size()) {
GLenum err = m_lostContextErrors.first();
m_lostContextErrors.remove(0);
return err;
}
if (isContextLost())
return GL_NO_ERROR;
return webContext()->getError();
}
const char* const* WebGLRenderingContextBase::ExtensionTracker::prefixes() const
{
static const char* const unprefixed[] = { "", 0, };
return m_prefixes ? m_prefixes : unprefixed;
}
bool WebGLRenderingContextBase::ExtensionTracker::matchesNameWithPrefixes(const String& name) const
{
const char* const* prefixSet = prefixes();
for (; *prefixSet; ++prefixSet) {
String prefixedName = String(*prefixSet) + extensionName();
if (equalIgnoringCase(prefixedName, name)) {
return true;
}
}
return false;
}
bool WebGLRenderingContextBase::extensionSupportedAndAllowed(const ExtensionTracker* tracker)
{
if (tracker->draft() && !RuntimeEnabledFeatures::webGLDraftExtensionsEnabled())
return false;
if (!tracker->supported(this))
return false;
return true;
}
ScriptValue WebGLRenderingContextBase::getExtension(ScriptState* scriptState, const String& name)
{
WebGLExtension* extension = nullptr;
if (!isContextLost()) {
for (size_t i = 0; i < m_extensions.size(); ++i) {
ExtensionTracker* tracker = m_extensions[i];
if (tracker->matchesNameWithPrefixes(name)) {
if (extensionSupportedAndAllowed(tracker)) {
extension = tracker->getExtension(this);
if (extension)
m_extensionEnabled[extension->name()] = true;
}
break;
}
}
}
return ScriptValue(scriptState, toV8(extension, scriptState->context()->Global(), scriptState->isolate()));
}
ScriptValue WebGLRenderingContextBase::getFramebufferAttachmentParameter(ScriptState* scriptState, GLenum target, GLenum attachment, GLenum pname)
{
if (isContextLost() || !validateFramebufferFuncParameters("getFramebufferAttachmentParameter", target, attachment))
return ScriptValue::createNull(scriptState);
if (!m_framebufferBinding || !m_framebufferBinding->object()) {
synthesizeGLError(GL_INVALID_OPERATION, "getFramebufferAttachmentParameter", "no framebuffer bound");
return ScriptValue::createNull(scriptState);
}
WebGLSharedObject* attachmentObject = m_framebufferBinding->getAttachmentObject(attachment);
if (!attachmentObject) {
if (pname == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)
return WebGLAny(scriptState, GL_NONE);
// OpenGL ES 2.0 specifies INVALID_ENUM in this case, while desktop GL
// specifies INVALID_OPERATION.
synthesizeGLError(GL_INVALID_ENUM, "getFramebufferAttachmentParameter", "invalid parameter name");
return ScriptValue::createNull(scriptState);
}
ASSERT(attachmentObject->isTexture() || attachmentObject->isRenderbuffer());
if (attachmentObject->isTexture()) {
switch (pname) {
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return WebGLAny(scriptState, GL_TEXTURE);
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
return WebGLAny(scriptState, attachmentObject);
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:
case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:
{
GLint value = 0;
webContext()->getFramebufferAttachmentParameteriv(target, attachment, pname, &value);
return WebGLAny(scriptState, value);
}
case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
if (extensionEnabled(EXTsRGBName)) {
GLint value = 0;
webContext()->getFramebufferAttachmentParameteriv(target, attachment, pname, &value);
return WebGLAny(scriptState, static_cast<unsigned>(value));
}
synthesizeGLError(GL_INVALID_ENUM, "getFramebufferAttachmentParameter", "invalid parameter name for renderbuffer attachment");
return ScriptValue::createNull(scriptState);
default:
synthesizeGLError(GL_INVALID_ENUM, "getFramebufferAttachmentParameter", "invalid parameter name for texture attachment");
return ScriptValue::createNull(scriptState);
}
} else {
switch (pname) {
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return WebGLAny(scriptState, GL_RENDERBUFFER);
case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
return WebGLAny(scriptState, attachmentObject);
case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
if (extensionEnabled(EXTsRGBName)) {
GLint value = 0;
webContext()->getFramebufferAttachmentParameteriv(target, attachment, pname, &value);
return WebGLAny(scriptState, value);
}
synthesizeGLError(GL_INVALID_ENUM, "getFramebufferAttachmentParameter", "invalid parameter name for renderbuffer attachment");
return ScriptValue::createNull(scriptState);
default:
synthesizeGLError(GL_INVAL